├── setup ├── __init__.py ├── uv_only.py └── premium.py ├── lib └── bpypolyskel │ └── __init__.py ├── pml ├── antlr4 │ ├── tree │ │ ├── __init__.py │ │ ├── Chunk.py │ │ ├── TokenTagToken.py │ │ ├── RuleTagToken.py │ │ └── ParseTreePattern.py │ ├── atn │ │ ├── __init__.py │ │ ├── ATNType.py │ │ ├── ATNDeserializationOptions.py │ │ └── ATNSimulator.py │ ├── dfa │ │ ├── __init__.py │ │ └── DFASerializer.py │ ├── error │ │ ├── __init__.py │ │ └── ErrorListener.py │ ├── xpath │ │ └── __init__.py │ ├── StdinStream.py │ ├── Utils.py │ ├── FileStream.py │ ├── __init__.py │ ├── CommonTokenFactory.py │ ├── CommonTokenStream.py │ └── InputStream.py ├── .gitattributes ├── tests │ ├── test_condition.py │ └── __init__.py ├── ExceptionManagement.py ├── pml_grammar │ ├── pml.tokens │ └── pmlLexer.tokens ├── examples │ ├── place_of_worship.pml │ ├── class_only.pml │ └── house.pml ├── __init__.py ├── PML2PythonTranslator.py └── README.md ├── threed_tiles ├── __init__.py ├── py3dtiles │ ├── __init__.py │ ├── tileset │ │ ├── extension │ │ │ ├── __init__.py │ │ │ └── base_extension.py │ │ ├── __init__.py │ │ ├── content │ │ │ ├── __init__.py │ │ │ ├── tile_content_reader.py │ │ │ ├── feature_table.py │ │ │ └── tile_content.py │ │ ├── utils.py │ │ ├── root_property.py │ │ └── bounding_volume.py │ ├── exceptions.py │ └── typing.py └── gltf_patch.py ├── item_renderer ├── texture │ ├── __init__.py │ ├── bottom.py │ ├── base │ │ ├── container.py │ │ ├── roof_flat_multi.py │ │ ├── __init__.py │ │ ├── level.py │ │ ├── door.py │ │ └── item_renderer.py │ ├── door.py │ ├── level.py │ ├── div.py │ ├── roof_hipped.py │ ├── roof_profile.py │ ├── export │ │ ├── level.py │ │ ├── __init__.py │ │ └── container.py │ ├── roof_pyramidal.py │ ├── roof_flat.py │ ├── roof_flat_multi.py │ └── facade.py ├── util.py └── test │ └── __init__.py ├── realistic ├── building │ ├── __init__.py │ ├── roof │ │ ├── pyramidal.py │ │ ├── half_hipped.py │ │ ├── mansard.py │ │ ├── mesh.py │ │ ├── hipped.py │ │ └── skillion.py │ ├── layer.py │ └── renderer.py └── material │ ├── __init__.py │ └── colors.py ├── util ├── blender_extra │ └── __init__.py ├── units.py ├── debug │ ├── debug_bpypolyskel.py.template │ ├── test_bpypolyskel.py.template │ └── __init__.py ├── __init__.py ├── osm.py ├── transverse_mercator.py └── random.py ├── grammar ├── units.py ├── symmetry.py ├── smoothness.py ├── arrangement.py ├── scope.py └── library.py ├── item ├── chimney.py ├── roof_generatrix.py ├── roof_flat_multi.py ├── roof_hipped_multi.py ├── roof_hipped.py ├── roof_profile.py ├── roof_flat.py ├── bottom.py ├── defs.py ├── balcony.py ├── door.py ├── div.py ├── window.py ├── facade.py ├── level.py ├── roof_item.py ├── roof_side.py └── __init__.py ├── assets ├── base.blend ├── roofs.blend └── way_profiles.blend ├── parse ├── __init__.py ├── osm │ ├── relation │ │ └── __init__.py │ └── node.py └── gpx │ └── __init__.py ├── README.md ├── action ├── __init__.py ├── way_cluster.py ├── volume │ ├── roof_gabled.py │ ├── roof_generatrix.py │ └── geometry │ │ └── __init__.py ├── offset.py └── terrain.py ├── mpl ├── __init__.py └── renderer.py ├── defs.py ├── way ├── __init__.py └── manager.py ├── renderer ├── node_layer.py └── curve_layer.py ├── overlay ├── arcgis.py └── mapbox.py ├── script ├── emission_for_windows.py ├── command_line.py ├── color_ramp_emission.py └── josm_complete_multipolygon_members.py ├── style └── __init__.py ├── geojson └── __init__.py ├── manager └── logging.py └── _blender_manifest.toml /setup/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/bpypolyskel/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pml/antlr4/tree/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /threed_tiles/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item_renderer/texture/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /realistic/building/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /realistic/material/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /util/blender_extra/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /grammar/units.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | Level = 1 -------------------------------------------------------------------------------- /pml/.gitattributes: -------------------------------------------------------------------------------- 1 | *.pml linguist-language=css -------------------------------------------------------------------------------- /pml/antlr4/atn/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'ericvergnaud' 2 | -------------------------------------------------------------------------------- /pml/antlr4/dfa/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'ericvergnaud' 2 | -------------------------------------------------------------------------------- /pml/antlr4/error/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'ericvergnaud' 2 | -------------------------------------------------------------------------------- /pml/antlr4/xpath/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'ericvergnaud' 2 | -------------------------------------------------------------------------------- /threed_tiles/py3dtiles/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "7.0.0" 2 | -------------------------------------------------------------------------------- /item/chimney.py: -------------------------------------------------------------------------------- 1 | from . import Item 2 | 3 | 4 | class Chimney(Item): 5 | pass -------------------------------------------------------------------------------- /assets/base.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prochitecture/blosm/HEAD/assets/base.blend -------------------------------------------------------------------------------- /assets/roofs.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prochitecture/blosm/HEAD/assets/roofs.blend -------------------------------------------------------------------------------- /grammar/symmetry.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | # constants for symmetry 4 | MiddleOfLast = 1 5 | RightmostOfLast = 2 -------------------------------------------------------------------------------- /assets/way_profiles.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prochitecture/blosm/HEAD/assets/way_profiles.blend -------------------------------------------------------------------------------- /item/roof_generatrix.py: -------------------------------------------------------------------------------- 1 | from .roof_flat import RoofFlat 2 | 3 | 4 | class RoofGeneratrix(RoofFlat): 5 | pass -------------------------------------------------------------------------------- /parse/__init__.py: -------------------------------------------------------------------------------- 1 | # types of data 2 | linestring = 1 3 | multilinestring = 2 4 | polygon = 3 5 | multipolygon = 4 -------------------------------------------------------------------------------- /grammar/smoothness.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | # constants for smoothness of building surfaces 4 | Smooth = 1 5 | Flat = 2 6 | Horizontal = 3 7 | Side = 4 8 | All = 5 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Important! 2 | Unless you are a developer, please get the addon [here](https://github.com/vvoovv/blender-osm/#blender-osm-openstreetmap-and-terrain-for-blender). 3 | -------------------------------------------------------------------------------- /item_renderer/texture/bottom.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Bottom: 4 | 5 | def __init__(self): 6 | pass 7 | 8 | def getNumLevelsInFace(self, levelGroup): 9 | return 1 -------------------------------------------------------------------------------- /grammar/arrangement.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | # constants for arrangement 4 | Vertical = 1 5 | Horizontal = 2 6 | # means that the items located like layers in graphics editor (i.e. one over another) 7 | Layers = 3 -------------------------------------------------------------------------------- /threed_tiles/py3dtiles/tileset/extension/__init__.py: -------------------------------------------------------------------------------- 1 | from .base_extension import BaseExtension 2 | from .batch_table_hierarchy_extension import BatchTableHierarchy 3 | 4 | __all__ = ["BaseExtension", "BatchTableHierarchy"] 5 | -------------------------------------------------------------------------------- /item_renderer/texture/base/container.py: -------------------------------------------------------------------------------- 1 | from .item_renderer import ItemRendererMixin 2 | from ..container import Container as ContainerBase 3 | 4 | 5 | class Container(ContainerBase, ItemRendererMixin): 6 | """ 7 | The base class for the item renderers Facade, Div, Layer, Bottom 8 | """ 9 | pass -------------------------------------------------------------------------------- /action/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Action: 4 | 5 | def __init__(self, app, data, itemStore, itemFactory): 6 | self.app = app 7 | self.data = data 8 | self.itemStore = itemStore 9 | self.itemFactory = itemFactory 10 | 11 | def cleanup(self): 12 | return -------------------------------------------------------------------------------- /item/roof_flat_multi.py: -------------------------------------------------------------------------------- 1 | from .roof_flat import RoofFlat 2 | 3 | 4 | class RoofFlatMulti(RoofFlat): 5 | 6 | def __init__(self): 7 | super().__init__() 8 | self.innerPolygons = [] 9 | 10 | def init(self): 11 | super().init() 12 | self.innerPolygons.clear() -------------------------------------------------------------------------------- /item/roof_hipped_multi.py: -------------------------------------------------------------------------------- 1 | from .roof_hipped import RoofHipped 2 | 3 | 4 | class RoofHippedMulti(RoofHipped): 5 | 6 | def __init__(self): 7 | super().__init__() 8 | self.innerPolygons = [] 9 | 10 | def init(self): 11 | super().init() 12 | self.innerPolygons.clear() -------------------------------------------------------------------------------- /pml/tests/test_condition.py: -------------------------------------------------------------------------------- 1 | from . import makeTest 2 | 3 | 4 | def test_style_variable(): 5 | makeTest( 6 | """ 7 | facade(item.front and style.count<=1) { 8 | class: facade_with_door; 9 | } 10 | """, 11 | """ 12 | Facade( 13 | condition = lambda item : item.front and self.count <= 1, 14 | cl = "facade_with_door" 15 | ) 16 | """ 17 | ) -------------------------------------------------------------------------------- /action/way_cluster.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class WayCluster: 4 | 5 | def __init__(self): 6 | pass 7 | 8 | def do(self, way): 9 | # is a building instance created by the parser 10 | # not to be confused with the building instance from 11 | pass 12 | 13 | def cleanup(self): 14 | pass -------------------------------------------------------------------------------- /item_renderer/texture/door.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Door: 4 | """ 5 | The Door renderer is the special case of the when 6 | a door in the only element in the level markup 7 | 8 | A mixin class for Door texture based item renderers 9 | """ 10 | 11 | def __init__(self): 12 | self.facadePatternInfo = dict(Door=1) -------------------------------------------------------------------------------- /pml/antlr4/StdinStream.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | import sys 3 | 4 | from antlr4.InputStream import InputStream 5 | 6 | 7 | class StdinStream(InputStream): 8 | def __init__(self, encoding:str='ascii', errors:str='strict') -> None: 9 | bytes = sys.stdin.buffer.read() 10 | data = codecs.decode(bytes, encoding, errors) 11 | super().__init__(data) 12 | -------------------------------------------------------------------------------- /item/roof_hipped.py: -------------------------------------------------------------------------------- 1 | from .roof_item import RoofWithSidesItem 2 | 3 | 4 | class RoofHipped(RoofWithSidesItem): 5 | 6 | @classmethod 7 | def getItem(cls, itemFactory, parent): 8 | item = itemFactory.getItem(cls) 9 | item.init() 10 | item.parent = parent 11 | item.footprint = parent 12 | item.setStyleBlock() 13 | item.building = parent.building 14 | return item -------------------------------------------------------------------------------- /item/roof_profile.py: -------------------------------------------------------------------------------- 1 | from .roof_item import RoofWithSidesItem 2 | 3 | 4 | class RoofProfile(RoofWithSidesItem): 5 | 6 | @classmethod 7 | def getItem(cls, itemFactory, parent): 8 | item = itemFactory.getItem(cls) 9 | item.init() 10 | item.parent = parent 11 | item.footprint = parent 12 | item.setStyleBlock() 13 | item.building = parent.building 14 | return item -------------------------------------------------------------------------------- /util/units.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | def fromUnits(value, units=None): 5 | """ 6 | The function is supposed to convert from the given to 7 | the internal system of units (meters) 8 | """ 9 | return value 10 | 11 | 12 | def toUnits(value, units): 13 | """ 14 | The function is supposed to convert from the internal system of units (meters) 15 | to the given 16 | """ 17 | return value -------------------------------------------------------------------------------- /item/roof_flat.py: -------------------------------------------------------------------------------- 1 | from .roof_item import RoofItem 2 | 3 | 4 | class RoofFlat(RoofItem): 5 | 6 | @classmethod 7 | def getItem(cls, itemFactory, parent, firstVertIndex): 8 | item = itemFactory.getItem(cls) 9 | item.init() 10 | item.parent = parent 11 | item.footprint = parent 12 | item.setStyleBlock() 13 | item.building = parent.building 14 | item.firstVertIndex = firstVertIndex 15 | return item -------------------------------------------------------------------------------- /item_renderer/texture/base/roof_flat_multi.py: -------------------------------------------------------------------------------- 1 | from .item_renderer import ItemRendererMixin 2 | from ..roof_flat_multi import RoofFlatMulti as RoofFlatMultiBase 3 | 4 | 5 | class RoofFlatMulti(RoofFlatMultiBase, ItemRendererMixin): 6 | 7 | def setVertexColor(self, item, faces): 8 | color = item.getCladdingColor() 9 | if color: 10 | for face in faces: 11 | self.r.setVertexColor(face, color, self.r.layer.vertexColorLayerNameCladding) -------------------------------------------------------------------------------- /pml/antlr4/atn/ATNType.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012-2017 The ANTLR Project. All rights reserved. 2 | # Use of this file is governed by the BSD 3-clause license that 3 | # can be found in the LICENSE.txt file in the project root. 4 | #/ 5 | 6 | from enum import IntEnum 7 | 8 | # Represents the type of recognizer an ATN applies to. 9 | 10 | class ATNType(IntEnum): 11 | 12 | LEXER = 0 13 | PARSER = 1 14 | 15 | @classmethod 16 | def fromOrdinal(cls, i:int): 17 | return cls._value2member_map_[i] 18 | -------------------------------------------------------------------------------- /threed_tiles/py3dtiles/tileset/__init__.py: -------------------------------------------------------------------------------- 1 | from .bounding_volume import BoundingVolume 2 | from .bounding_volume_box import BoundingVolumeBox 3 | from .root_property import RootProperty 4 | from .tile import Tile 5 | from .tileset import TileSet 6 | from .utils import number_of_points_in_tileset 7 | 8 | __all__ = [ 9 | "BoundingVolume", 10 | "BoundingVolumeBox", 11 | "content", 12 | "extension", 13 | "number_of_points_in_tileset", 14 | "RootProperty", 15 | "Tile", 16 | "TileSet", 17 | ] 18 | -------------------------------------------------------------------------------- /item/bottom.py: -------------------------------------------------------------------------------- 1 | from .container import Container 2 | 3 | 4 | class Bottom(Container): 5 | 6 | def __init__(self): 7 | super().__init__() 8 | self.buildingPart = "bottom" 9 | 10 | @classmethod 11 | def getItem(cls, itemFactory, parent, styleBlock): 12 | item = itemFactory.getItem(cls) 13 | item.init() 14 | item.parent = parent 15 | item.footprint = parent.footprint 16 | item.building = parent.building 17 | item.styleBlock = styleBlock 18 | return item -------------------------------------------------------------------------------- /item/defs.py: -------------------------------------------------------------------------------- 1 | 2 | RoofShapes = { 3 | "flat": 1, 4 | "gabled": 1, 5 | "hipped": 1, 6 | "pyramidal": 1, 7 | #"skillion": 1, 8 | "round": 1, 9 | "dome": 1, 10 | "half-dome": 1, 11 | "onion": 1, 12 | "gambrel": 1, 13 | "saltbox": 1 14 | } 15 | 16 | 17 | CladdingMaterials = { 18 | "brick": 1, 19 | "plaster": 1, 20 | "glass": 1, 21 | "concrete": 1, 22 | "gravel": 1, 23 | "metal": 1, 24 | "roof_tiles": 1 25 | } 26 | 27 | 28 | RoofOrientation = { 29 | "along", 30 | "across" 31 | } -------------------------------------------------------------------------------- /item_renderer/texture/level.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Level: 4 | 5 | def __init__(self): 6 | pass 7 | 8 | def init(self, itemRenderers, globalRenderer): 9 | self.Container.init(self, itemRenderers, globalRenderer) 10 | 11 | def getNumLevelsInFace(self, levelGroup): 12 | return 1 if levelGroup.singleLevel else (levelGroup.index2-levelGroup.index1+1) 13 | 14 | 15 | class CurtainWall(Level): 16 | 17 | def __init__(self): 18 | super().__init__() 19 | 20 | self.claddingTexture = False -------------------------------------------------------------------------------- /item_renderer/texture/div.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Div: 4 | """ 5 | A mixin class for Div texture based item renderers 6 | """ 7 | 8 | def init(self, itemRenderers, globalRenderer): 9 | self.Container.init(self, itemRenderers, globalRenderer) 10 | self.bottomRenderer = itemRenderers["Bottom"] 11 | 12 | def render(self, item, indices, uvs): 13 | if item.markup: 14 | item.indices = indices 15 | item.uvs = uvs 16 | self.renderMarkup(item) 17 | else: 18 | self.r.createFace(item.building, indices) -------------------------------------------------------------------------------- /action/volume/roof_gabled.py: -------------------------------------------------------------------------------- 1 | from .roof_profile import RoofProfile 2 | 3 | 4 | # Use https://raw.githubusercontent.com/wiki/vvoovv/blosm/assets/roof_profiles.blend 5 | # to generate values for a specific profile 6 | _gabledRoof = ( 7 | ( 8 | (0., 0.), 9 | (0.5, 1.), 10 | (1., 0.) 11 | ), 12 | { 13 | "numSamples": 10, 14 | "angleToHeight": 0.5 15 | } 16 | ) 17 | 18 | 19 | class RoofGabled(RoofProfile): 20 | 21 | # default roof height 22 | height = 4. 23 | 24 | def __init__(self): 25 | super().__init__(_gabledRoof) -------------------------------------------------------------------------------- /threed_tiles/py3dtiles/tileset/content/__init__.py: -------------------------------------------------------------------------------- 1 | from .b3dm import B3dm, B3dmBody, B3dmHeader 2 | from .gltf import GlTF 3 | from .pnts import Pnts, PntsBody, PntsHeader 4 | from .tile_content import TileContent, TileContentBody, TileContentHeader 5 | from .tile_content_reader import read_binary_tile_content 6 | 7 | __all__ = [ 8 | "batch_table", 9 | "B3dm", 10 | "B3dmBody", 11 | "B3dmHeader", 12 | "feature_table", 13 | "GlTF", 14 | "Pnts", 15 | "PntsBody", 16 | "PntsHeader", 17 | "read_binary_tile_content", 18 | "TileContent", 19 | "TileContentBody", 20 | "TileContentHeader", 21 | ] 22 | -------------------------------------------------------------------------------- /item/balcony.py: -------------------------------------------------------------------------------- 1 | from . import Item 2 | 3 | 4 | class Balcony(Item): 5 | 6 | # default values 7 | width = 2.4 8 | marginLeft = 1. 9 | marginRight = 1. 10 | 11 | @classmethod 12 | def getItem(cls, itemFactory, parent, styleBlock): 13 | item = itemFactory.getItem(cls) 14 | item.init() 15 | item.parent = parent 16 | item.footprint = parent.footprint 17 | item.building = parent.building 18 | item.styleBlock = styleBlock 19 | return item 20 | 21 | def getWidth(self): 22 | return Balcony.marginLeft + (self.getStyleBlockAttr("width") or Balcony.width) + Balcony.marginRight -------------------------------------------------------------------------------- /item/door.py: -------------------------------------------------------------------------------- 1 | from . import Item 2 | 3 | 4 | class Door(Item): 5 | 6 | # default values 7 | width = 1.2 8 | height = 2. 9 | marginLeft = 1. 10 | marginRight = 1. 11 | 12 | @classmethod 13 | def getItem(cls, itemFactory, parent, styleBlock): 14 | item = itemFactory.getItem(cls) 15 | item.init() 16 | item.parent = parent 17 | item.footprint = parent.footprint 18 | item.building = parent.building 19 | item.styleBlock = styleBlock 20 | return item 21 | 22 | def getWidth(self): 23 | return Door.marginLeft + (self.getStyleBlockAttr("width") or Door.width) + Door.marginRight -------------------------------------------------------------------------------- /mpl/__init__.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | 3 | 4 | class Mpl: 5 | """ 6 | A wrapper for matplotlib 7 | """ 8 | mpl = None 9 | 10 | def __init__(self): 11 | self.shown = False 12 | fig = plt.figure() 13 | self.ax = fig.gca() 14 | 15 | def show(self): 16 | if not self.shown: 17 | self.shown = True 18 | self.ax.axis('equal') 19 | plt.show() 20 | 21 | @staticmethod 22 | def getMpl(): 23 | if not Mpl.mpl: 24 | Mpl.mpl = Mpl() 25 | return Mpl.mpl 26 | 27 | @staticmethod 28 | def cleanup(): 29 | Mpl.mpl = None -------------------------------------------------------------------------------- /item/div.py: -------------------------------------------------------------------------------- 1 | from .container import Container 2 | from .level_groups import LevelGroups 3 | 4 | 5 | class Div(Container): 6 | 7 | def __init__(self): 8 | super().__init__() 9 | self.levelGroups = LevelGroups(self) 10 | 11 | def init(self): 12 | super().init() 13 | self.levelGroups.clear() 14 | 15 | @classmethod 16 | def getItem(cls, itemFactory, parent, styleBlock): 17 | item = itemFactory.getItem(cls) 18 | item.init() 19 | item.parent = parent 20 | item.footprint = parent.footprint 21 | item.building = parent.building 22 | item.styleBlock = styleBlock 23 | return item -------------------------------------------------------------------------------- /action/volume/roof_generatrix.py: -------------------------------------------------------------------------------- 1 | from .roof_flat import RoofLeveled 2 | from item.roof_generatrix import RoofGeneratrix as ItemRoofGeneratrix 3 | 4 | 5 | class RoofGeneratrix(RoofLeveled): 6 | 7 | height = 4. 8 | 9 | def __init__(self, roofRendererId, data, volumeAction, itemRenderers): 10 | super().__init__(roofRendererId, data, volumeAction, itemRenderers) 11 | self.hasRoofLevels = False 12 | self.extrudeTillRoof = True 13 | 14 | def getRoofItem(self, footprint): 15 | return ItemRoofGeneratrix.getItem( 16 | self.itemFactory, 17 | footprint, 18 | self.getRoofFirstVertIndex(footprint) 19 | ) -------------------------------------------------------------------------------- /item/window.py: -------------------------------------------------------------------------------- 1 | from . import Item 2 | 3 | 4 | class Window(Item): 5 | 6 | # default values 7 | width = 1.2 8 | height = 1.8 9 | marginLeft = 1. 10 | marginRight = 1. 11 | 12 | @classmethod 13 | def getItem(cls, itemFactory, parent, styleBlock): 14 | item = itemFactory.getItem(cls) 15 | item.init() 16 | item.parent = parent 17 | item.footprint = parent.footprint 18 | item.building = parent.building 19 | item.styleBlock = styleBlock 20 | return item 21 | 22 | def getWidth(self): 23 | return Window.marginLeft + (self.getStyleBlockAttr("width") or Window.width) + Window.marginRight 24 | 25 | def getMargin(self): 26 | return Window.marginLeft + Window.marginRight -------------------------------------------------------------------------------- /pml/antlr4/tree/Chunk.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2012-2017 The ANTLR Project. All rights reserved. 3 | # Use of this file is governed by the BSD 3-clause license that 4 | # can be found in the LICENSE.txt file in the project root. 5 | # 6 | 7 | class Chunk(object): 8 | pass 9 | 10 | class TagChunk(Chunk): 11 | 12 | def __init__(self, tag:str, label:str=None): 13 | self.tag = tag 14 | self.label = label 15 | 16 | def __str__(self): 17 | if self.label is None: 18 | return self.tag 19 | else: 20 | return self.label + ":" + self.tag 21 | 22 | class TextChunk(Chunk): 23 | 24 | def __init__(self, text:str): 25 | self.text = text 26 | 27 | def __str__(self): 28 | return "'" + self.text + "'" 29 | 30 | -------------------------------------------------------------------------------- /util/debug/debug_bpypolyskel.py.template: -------------------------------------------------------------------------------- 1 | import pytest 2 | from mathutils import Vector 3 | from bpypolyskel import bpypolyskel 4 | 5 | 6 | verts = ${verts} 7 | unitVectors = ${unitVectors} 8 | holesInfo = ${holesInfo} 9 | firstVertIndex = ${firstVertIndex} 10 | numPolygonVerts = ${numPolygonVerts} 11 | 12 | bpypolyskel.debugOutputs["skeleton"] = 1 13 | 14 | 15 | faces = bpypolyskel.polygonize(verts, firstVertIndex, numPolygonVerts, holesInfo, 0.0, 0.5, None, unitVectors) 16 | 17 | 18 | # the number of vertices in a face 19 | for face in faces: 20 | assert len(face) >= 3 21 | 22 | 23 | # duplications of vertex indices 24 | for face in faces: 25 | assert len(face) == len(set(face)) 26 | 27 | 28 | # edge crossing 29 | assert not bpypolyskel.checkEdgeCrossing(bpypolyskel.debugOutputs["skeleton"]) -------------------------------------------------------------------------------- /item_renderer/texture/roof_hipped.py: -------------------------------------------------------------------------------- 1 | from .. import ItemRenderer 2 | 3 | 4 | class RoofHipped(ItemRenderer): 5 | 6 | def render(self, roofItem): 7 | building = roofItem.building 8 | 9 | for roofSide in roofItem.roofSides: 10 | face = self.r.createFace( 11 | building, 12 | roofSide.indices 13 | ) 14 | 15 | cl = roofSide.getStyleBlockAttr("cl") 16 | if cl: 17 | self.renderClass(roofSide, cl, face, roofSide.uvs) 18 | else: 19 | self.renderCladding(roofSide, face, roofSide.uvs) 20 | 21 | def setClassUvs(self, item, face, uvs, texUl, texVb, texUr, texVt): 22 | # convert generator to Python tuple: 23 | self._setRoofClassUvs(face, tuple(uvs), texUl, texVb, texUr, texVt) -------------------------------------------------------------------------------- /grammar/scope.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Constants for the scope. 4 | perBuilding = 1 5 | perFootprint = 2 6 | 7 | 8 | class Scope: 9 | """ 10 | The base class for the wrapper classes below. 11 | 12 | An attribute in the style block can be defined in the form , 13 | where is one of the classes defined below and, 14 | is an instance of 15 | """ 16 | 17 | def __init__(self, value): 18 | self.value = value 19 | 20 | 21 | class PerBuilding(Scope): 22 | """ 23 | A wrapper class for the scope. 24 | is evaluated once per building 25 | """ 26 | scope = perBuilding 27 | 28 | 29 | class PerFootprint(Scope): 30 | """ 31 | A wrapper class for the scope. 32 | is evaluated for every footprint in the building 33 | """ 34 | scope = perFootprint -------------------------------------------------------------------------------- /pml/tests/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is also needed for the correct work of relative imports in the tests. For example: 3 | from . import getPythonCode 4 | from .. import PML 5 | """ 6 | 7 | from .. import PML 8 | import os, ast, tempfile 9 | 10 | 11 | def compare(output, referenceOutput): 12 | referenceOutput = "styles = [%s]" % referenceOutput 13 | return ''.join(output.split()) == ''.join(referenceOutput.split()) 14 | 15 | 16 | def getPythonCode(pmlString, rootDir=''): 17 | with tempfile.TemporaryDirectory() as tmpDir: 18 | tmpFileName = os.path.join(tmpDir, "test.pml") 19 | with open(tmpFileName, 'w') as tmpFile: 20 | tmpFile.write(pmlString) 21 | pythonCode = PML(tmpFileName, rootDir).getPythonCode() 22 | return pythonCode 23 | 24 | 25 | def makeTest(input, referenceOutput): 26 | output = getPythonCode(input) 27 | ast.parse(output) 28 | assert compare(output, referenceOutput) -------------------------------------------------------------------------------- /defs.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of Blosm addon for Blender. 3 | Copyright (C) 2014-2018 Vladimir Elistratov 4 | prokitektura+support@gmail.com 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | class Keys: 21 | mode3d = "mode3d" 22 | mode3dRealistic = "mode3dRealistic" 23 | overlay = "overlay" 24 | geojson = "geojson" 25 | -------------------------------------------------------------------------------- /threed_tiles/py3dtiles/tileset/extension/base_extension.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from abc import ABC, abstractmethod 4 | 5 | from py3dtiles.typing import ExtensionDictType 6 | 7 | 8 | class BaseExtension(ABC): 9 | """ 10 | A base class to manage 3dtiles extension. 11 | 12 | If an extension is added somewhere in a tileset, 13 | the user must add the name of the extension in the attribute `extensions_used` of the class `TileSet`. 14 | Also, if the support of an extension is necessary to display the tileset, 15 | the name must be added in the attribute `extensions_required` of the class `TileSet`. 16 | """ 17 | 18 | def __init__(self, name: str): 19 | self.name = name 20 | 21 | @classmethod 22 | @abstractmethod 23 | def from_dict(cls, extension_dict: ExtensionDictType) -> BaseExtension: 24 | ... 25 | 26 | @abstractmethod 27 | def to_dict(self) -> ExtensionDictType: 28 | ... 29 | -------------------------------------------------------------------------------- /way/__init__.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | 3 | 4 | class RealWay: 5 | 6 | def __init__(self, element): 7 | self.element = element 8 | self.polyline = None 9 | 10 | def segments(self, data): 11 | # segmentCenter, segmentUnitVector, segmentLength 12 | coord0 = coord1 = None 13 | for coord2 in ( numpy.array((coord[0], coord[1])) for coord in self.element.getData(data) ): 14 | if coord1 is None: 15 | coord0 = coord2 16 | else: 17 | segmentVector = coord2 - coord1 18 | segmentLength = numpy.linalg.norm(segmentVector) 19 | yield (coord1 + coord2)/2., segmentVector/segmentLength, segmentLength 20 | coord1 = coord2 21 | if self.element.isClosed(): 22 | segmentVector = coord0 - coord1 23 | segmentLength = numpy.linalg.norm(segmentVector) 24 | yield (coord1 + coord0)/2., segmentVector/segmentLength, segmentLength -------------------------------------------------------------------------------- /item_renderer/texture/roof_profile.py: -------------------------------------------------------------------------------- 1 | from .. import ItemRenderer 2 | from grammar import smoothness 3 | 4 | 5 | class RoofProfile(ItemRenderer): 6 | 7 | def render(self, roofItem): 8 | building = roofItem.building 9 | smoothFaces = roofItem.getStyleBlockAttr("faces") is smoothness.Smooth 10 | 11 | for roofSide in roofItem.roofSides: 12 | face = self.r.createFace( 13 | building, 14 | roofSide.indices 15 | ) 16 | if smoothFaces: 17 | face.smooth = True 18 | cl = roofSide.getStyleBlockAttr("cl") 19 | if cl: 20 | self.renderClass(roofSide, cl, face, roofSide.uvs) 21 | else: 22 | self.renderCladding(roofSide, face, roofSide.uvs) 23 | 24 | def setClassUvs(self, item, face, uvs, texUl, texVb, texUr, texVt): 25 | # convert generator to Python tuple: 26 | self._setRoofClassUvs(face, tuple(uvs), texUl, texVb, texUr, texVt) -------------------------------------------------------------------------------- /pml/ExceptionManagement.py: -------------------------------------------------------------------------------- 1 | # patched ErrorListener from antlr4 2 | class ParserExceptionListener(object): 3 | def syntaxError(self, recognizer, offendingSymbol, line, col, msg, e): 4 | raise ParserException(line, col, msg) 5 | 6 | def reportAmbiguity(self, recognizer, dfa, startIndex, stopIndex, exact, ambigAlts, configs): 7 | pass 8 | 9 | def reportAttemptingFullContext(self, recognizer, dfa, startIndex, stopIndex, conflictingAlts, configs): 10 | pass 11 | 12 | def reportContextSensitivity(self, recognizer, dfa, startIndex, stopIndex, prediction, configs): 13 | pass 14 | 15 | 16 | class ParserException(Exception): 17 | def __init__(self, line, col, msg): 18 | self.line = line 19 | self.col = col 20 | self.msg = msg 21 | 22 | def errParams(self): 23 | return self.line, self.col, self.msg 24 | 25 | def __str__(self): 26 | return 'Error on line {line}, col {col}: {msg}'.format( 27 | line = self.line, col = self.col, msg = self.msg) -------------------------------------------------------------------------------- /util/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of Blosm addon for Blender. 3 | Copyright (C) 2014-2018 Vladimir Elistratov 4 | prokitektura+support@gmail.com 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | from mathutils import Vector 21 | 22 | xAxis = Vector((1., 0., 0.)) 23 | yAxis = Vector((0., 1., 0.)) 24 | zAxis = Vector((0., 0., 1.)) 25 | 26 | def zeroVector(): 27 | return Vector((0., 0., 0.)) 28 | 29 | zero = .001 # 1mm -------------------------------------------------------------------------------- /pml/antlr4/atn/ATNDeserializationOptions.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012-2017 The ANTLR Project. All rights reserved. 2 | # Use of this file is governed by the BSD 3-clause license that 3 | # can be found in the LICENSE.txt file in the project root. 4 | 5 | # need a forward declaration 6 | ATNDeserializationOptions = None 7 | 8 | class ATNDeserializationOptions(object): 9 | 10 | defaultOptions = None 11 | 12 | def __init__(self, copyFrom:ATNDeserializationOptions = None): 13 | self.readOnly = False 14 | self.verifyATN = True if copyFrom is None else copyFrom.verifyATN 15 | self.generateRuleBypassTransitions = False if copyFrom is None else copyFrom.generateRuleBypassTransitions 16 | 17 | def __setattr__(self, key, value): 18 | if key!="readOnly" and self.readOnly: 19 | raise Exception("The object is read only.") 20 | super(type(self), self).__setattr__(key,value) 21 | 22 | ATNDeserializationOptions.defaultOptions = ATNDeserializationOptions() 23 | ATNDeserializationOptions.defaultOptions.readOnly = True 24 | 25 | -------------------------------------------------------------------------------- /pml/antlr4/Utils.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012-2017 The ANTLR Project. All rights reserved. 2 | # Use of this file is governed by the BSD 3-clause license that 3 | # can be found in the LICENSE.txt file in the project root. 4 | # 5 | 6 | from io import StringIO 7 | 8 | def str_list(val): 9 | with StringIO() as buf: 10 | buf.write('[') 11 | first = True 12 | for item in val: 13 | if not first: 14 | buf.write(', ') 15 | buf.write(str(item)) 16 | first = False 17 | buf.write(']') 18 | return buf.getvalue() 19 | 20 | def escapeWhitespace(s:str, escapeSpaces:bool): 21 | with StringIO() as buf: 22 | for c in s: 23 | if c==' ' and escapeSpaces: 24 | buf.write('\u00B7') 25 | elif c=='\t': 26 | buf.write("\\t") 27 | elif c=='\n': 28 | buf.write("\\n") 29 | elif c=='\r': 30 | buf.write("\\r") 31 | else: 32 | buf.write(c) 33 | return buf.getvalue() 34 | -------------------------------------------------------------------------------- /util/debug/test_bpypolyskel.py.template: -------------------------------------------------------------------------------- 1 | import pytest 2 | from mathutils import Vector 3 | from bpypolyskel import bpypolyskel 4 | 5 | 6 | verts = ${verts} 7 | unitVectors = ${unitVectors} 8 | holesInfo = ${holesInfo} 9 | firstVertIndex = ${firstVertIndex} 10 | numPolygonVerts = ${numPolygonVerts} 11 | faces = [] 12 | 13 | bpypolyskel.debugOutputs["skeleton"] = 1 14 | 15 | 16 | @pytest.mark.dependency() 17 | @pytest.mark.timeout(10) 18 | def test_polygonize(): 19 | global faces 20 | faces = bpypolyskel.polygonize(verts, firstVertIndex, numPolygonVerts, holesInfo, 0.0, 0.5, None, unitVectors) 21 | 22 | 23 | @pytest.mark.dependency(depends=["test_polygonize"]) 24 | def test_numVertsInFace(): 25 | for face in faces: 26 | assert len(face) >= 3 27 | 28 | 29 | @pytest.mark.dependency(depends=["test_polygonize"]) 30 | def test_duplication(): 31 | for face in faces: 32 | assert len(face) == len(set(face)) 33 | 34 | 35 | @pytest.mark.dependency(depends=["test_polygonize"]) 36 | def test_edgeCrossing(): 37 | assert not bpypolyskel.checkEdgeCrossing(bpypolyskel.debugOutputs["skeleton"]) -------------------------------------------------------------------------------- /renderer/node_layer.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of Blosm addon for Blender. 3 | Copyright (C) 2014-2018 Vladimir Elistratov 4 | prokitektura+support@gmail.com 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | from .layer import Layer 21 | from util import zeroVector 22 | 23 | 24 | class NodeLayer(Layer): 25 | 26 | def __init__(self, layerId, app): 27 | super().__init__(layerId, app) 28 | self.parentLocation = zeroVector() 29 | 30 | def init(self): 31 | pass -------------------------------------------------------------------------------- /realistic/building/roof/pyramidal.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of Blosm addon for Blender. 3 | Copyright (C) 2014-2018 Vladimir Elistratov 4 | prokitektura+support@gmail.com 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | from . import RoofRealistic 21 | from building.roof.pyramidal import RoofPyramidal 22 | from .flat import RoofFlatRealistic 23 | 24 | 25 | class RoofPyramidalRealistic(RoofRealistic, RoofPyramidal): 26 | 27 | def renderWalls(self): 28 | RoofFlatRealistic.renderWalls(self) -------------------------------------------------------------------------------- /action/volume/geometry/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Geometry: 4 | 5 | def initRenderStateForDivs(self, rs, item): 6 | rs.indexLB = item.indices[0] 7 | rs.indexLT = item.indices[-1] 8 | rs.texUl = item.uvs[0][0] 9 | rs.texVlt = item.uvs[-1][1] 10 | 11 | def initRenderStateForLevels(self, rs, parentItem): 12 | parentIndices = parentItem.indices 13 | # and are indices of the bottom vertices of an level item to be created 14 | # The prefix means "bottom left" 15 | rs.indexBL = parentIndices[0] 16 | # The prefix
means "bottom rights" 17 | rs.indexBR = parentIndices[1] 18 | # is the current V-coordinate for texturing the bottom vertices of 19 | # level items to be created out of 20 | rs.texVb = parentItem.uvs[0][1] 21 | 22 | def renderBottom(self, parentItem, parentRenderer, rs): 23 | bottom = parentItem.levelGroups.bottom 24 | if bottom: 25 | self.renderLevelGroup( 26 | parentItem, bottom, parentRenderer.bottomRenderer, rs 27 | ) -------------------------------------------------------------------------------- /realistic/building/roof/half_hipped.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of Blosm addon for Blender. 3 | Copyright (C) 2014-2018 Vladimir Elistratov 4 | prokitektura+support@gmail.com 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | from . import RoofRealistic 21 | from building.roof.half_hipped import RoofHalfHipped 22 | from .profile import RoofProfileRealistic 23 | 24 | 25 | class RoofHalfHippedRealistic(RoofRealistic, RoofHalfHipped): 26 | 27 | def renderWalls(self): 28 | RoofProfileRealistic.renderWalls(self) -------------------------------------------------------------------------------- /util/osm.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of Blosm addon for Blender. 3 | Copyright (C) 2014-2018 Vladimir Elistratov 4 | prokitektura+support@gmail.com 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | def assignTags(obj, tags): 21 | for key in tags: 22 | obj[key] = tags[key] 23 | 24 | 25 | def parseNumber(s, defaultValue=None): 26 | s = s.rstrip() 27 | if s[-1] == 'm': 28 | s = s[:-1] 29 | try: 30 | n = float(s) 31 | except ValueError: 32 | n = defaultValue 33 | return n 34 | -------------------------------------------------------------------------------- /pml/antlr4/FileStream.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2012-2017 The ANTLR Project. All rights reserved. 3 | # Use of this file is governed by the BSD 3-clause license that 4 | # can be found in the LICENSE.txt file in the project root. 5 | # 6 | 7 | # 8 | # This is an InputStream that is loaded from a file all at once 9 | # when you construct the object. 10 | # 11 | 12 | import codecs 13 | import unittest 14 | from antlr4.InputStream import InputStream 15 | 16 | 17 | class FileStream(InputStream): 18 | 19 | def __init__(self, fileName:str, encoding:str='ascii', errors:str='strict'): 20 | super().__init__(self.readDataFrom(fileName, encoding, errors)) 21 | self.fileName = fileName 22 | 23 | def readDataFrom(self, fileName:str, encoding:str, errors:str='strict'): 24 | # read binary to avoid line ending conversion 25 | with open(fileName, 'rb') as file: 26 | bytes = file.read() 27 | return codecs.decode(bytes, encoding, errors) 28 | 29 | 30 | class TestFileStream(unittest.TestCase): 31 | 32 | def testStream(self): 33 | stream = FileStream(__file__) 34 | self.assertTrue(stream.size>0) 35 | -------------------------------------------------------------------------------- /pml/antlr4/__init__.py: -------------------------------------------------------------------------------- 1 | from antlr4.Token import Token 2 | from antlr4.InputStream import InputStream 3 | from antlr4.FileStream import FileStream 4 | from antlr4.StdinStream import StdinStream 5 | from antlr4.BufferedTokenStream import TokenStream 6 | from antlr4.CommonTokenStream import CommonTokenStream 7 | from antlr4.Lexer import Lexer 8 | from antlr4.Parser import Parser 9 | from antlr4.dfa.DFA import DFA 10 | from antlr4.atn.ATN import ATN 11 | from antlr4.atn.ATNDeserializer import ATNDeserializer 12 | from antlr4.atn.LexerATNSimulator import LexerATNSimulator 13 | from antlr4.atn.ParserATNSimulator import ParserATNSimulator 14 | from antlr4.atn.PredictionMode import PredictionMode 15 | from antlr4.PredictionContext import PredictionContextCache 16 | from antlr4.ParserRuleContext import RuleContext, ParserRuleContext 17 | from antlr4.tree.Tree import ParseTreeListener, ParseTreeVisitor, ParseTreeWalker, TerminalNode, ErrorNode, RuleNode 18 | from antlr4.error.Errors import RecognitionException, IllegalStateException, NoViableAltException 19 | from antlr4.error.ErrorStrategy import BailErrorStrategy 20 | from antlr4.error.DiagnosticErrorListener import DiagnosticErrorListener 21 | from antlr4.Utils import str_list 22 | -------------------------------------------------------------------------------- /item_renderer/util.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | 4 | 5 | def initUvAlongPolygonEdge(polygon, index0, index1): 6 | """ 7 | Returns: 8 | tuple: uVec, uv0, uv1 9 | uVec: A unit vector the polygon edge defined by the indices and 10 | uv0: UV-coordinates for the point at the polygon vertex 11 | uv1: UV-coordinates for the point at the polygon vertex 12 | """ 13 | uVec = polygon.allVerts[polygon.indices[index1]] - polygon.allVerts[polygon.indices[index0]] 14 | uv0 = (0., 0.) 15 | uv1 = (uVec.length, 0.) 16 | uVec /= uv1[0] 17 | return uVec, uv0, uv1 18 | 19 | 20 | def setTextureSize(assetInfo, image): 21 | assetInfo["textureSize"] = tuple(image.size) 22 | 23 | def setTextureSize2(assetInfo, materialName, imageName): 24 | if not "textureSize" in assetInfo: 25 | assetInfo["textureSize"] = tuple( 26 | bpy.data.materials[materialName].node_tree.nodes.get(imageName).image.size 27 | ) 28 | 29 | 30 | def getPath(globalRenderer, path): 31 | if path[0] == '/': 32 | return os.path.join(globalRenderer.assetsDir, path[1:]) 33 | else: 34 | return os.path.join(globalRenderer.assetPackageDir, path) -------------------------------------------------------------------------------- /setup/uv_only.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of Blosm addon for Blender. 3 | Copyright (C) 2014-2018 Vladimir Elistratov 4 | prokitektura+support@gmail.com 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | from setup.premium import setup_base 21 | 22 | def setup(app, osm): 23 | setup_base(app, osm, getMaterials, bldgPreRender) 24 | 25 | 26 | from realistic.material.renderer import UvOnly 27 | 28 | 29 | def getMaterials(): 30 | return dict( 31 | uv_only = UvOnly 32 | ) 33 | 34 | 35 | def bldgPreRender(building, app): 36 | building.setMaterialWalls("uv_only") 37 | building.setMaterialRoof("uv_only") -------------------------------------------------------------------------------- /action/offset.py: -------------------------------------------------------------------------------- 1 | from mathutils import Vector 2 | from . import Action 3 | import parse 4 | 5 | 6 | class Offset(Action): 7 | """ 8 | Calculates offset for a building if the option "Import as a single object" is NOT activated. 9 | Also creates a Blender object for the building in question and positions it appropriately. 10 | """ 11 | 12 | def preprocess(self, buildingsP): 13 | # means "buildings from the parser" 14 | return 15 | 16 | def do(self, building, itemClass, style, globalRenderer): 17 | outline = building.outline 18 | offset = Vector( 19 | next( 20 | outline.getOuterData(self.data) if outline.t is parse.multipolygon else outline.getData(self.data) 21 | ) 22 | ) 23 | 24 | layer = outline.l 25 | globalRenderer.obj = globalRenderer.createBlenderObject( 26 | globalRenderer.getName(outline), 27 | offset+building.offset if building.offset else offset, 28 | collection = layer.getCollection(globalRenderer.collection), 29 | parent = layer.getParent( layer.getCollection(globalRenderer.collection) ) 30 | ) 31 | layer.prepare(globalRenderer) 32 | 33 | building.offset = -offset -------------------------------------------------------------------------------- /threed_tiles/py3dtiles/tileset/content/tile_content_reader.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from pathlib import Path 4 | from typing import TYPE_CHECKING 5 | 6 | import numpy as np 7 | import numpy.typing as npt 8 | 9 | from py3dtiles.exceptions import Invalid3dtilesError 10 | 11 | from .b3dm import B3dm 12 | from .pnts import Pnts 13 | 14 | if TYPE_CHECKING: 15 | from py3dtiles.tileset.content import TileContent 16 | 17 | __all__ = ["read_binary_tile_content"] 18 | 19 | 20 | def read_binary_tile_content(tile_path: Path) -> TileContent: 21 | with tile_path.open("rb") as f: 22 | data = f.read() 23 | arr = np.frombuffer(data, dtype=np.uint8) 24 | 25 | tile_content = read_array(arr) 26 | if tile_content is None or tile_content.header is None: 27 | raise Invalid3dtilesError( 28 | f"The file {tile_path} doesn't contain a valid TileContent data." 29 | ) 30 | 31 | return tile_content 32 | 33 | 34 | def read_array(array: npt.NDArray[np.uint8]) -> TileContent | None: 35 | magic = "".join([c.decode("UTF-8") for c in array[0:4].view("c")]) 36 | if magic == "pnts": 37 | return Pnts.from_array(array) 38 | if magic == "b3dm": 39 | return B3dm.from_array(array) 40 | return None 41 | -------------------------------------------------------------------------------- /pml/pml_grammar/pml.tokens: -------------------------------------------------------------------------------- 1 | T__0=1 2 | T__1=2 3 | T__2=3 4 | T__3=4 5 | T__4=5 6 | T__5=6 7 | T__6=7 8 | T__7=8 9 | T__8=9 10 | T__9=10 11 | T__10=11 12 | T__11=12 13 | T__12=13 14 | T__13=14 15 | T__14=15 16 | T__15=16 17 | T__16=17 18 | T__17=18 19 | T__18=19 20 | T__19=20 21 | OR=21 22 | AND=22 23 | NOT=23 24 | IN=24 25 | IDENTIFIER=25 26 | STRING_LITERAL=26 27 | HEX_NUMBER=27 28 | NUMBER=28 29 | FLOAT=29 30 | INT=30 31 | STRUDEL=31 32 | LCURLY=32 33 | RCURLY=33 34 | LPAREN=34 35 | RPAREN=35 36 | LBRACK=36 37 | RBRACK=37 38 | PIPE=38 39 | COMMA=39 40 | COLON=40 41 | SEMI=41 42 | PLUS=42 43 | MINUS=43 44 | TIMES=44 45 | DIV=45 46 | GT=46 47 | GE=47 48 | LT=48 49 | LE=49 50 | EQ=50 51 | COMMENT=51 52 | WS=52 53 | '@name'=1 54 | 'level'=2 55 | 'symmetry'=3 56 | 'use'=4 57 | 'faces'=5 58 | 'sharpEdges'=6 59 | 'attr'=7 60 | 'bldgAttr'=8 61 | 'random_normal'=9 62 | 'random_weighted'=10 63 | 'if'=11 64 | 'use_from'=12 65 | 'per_building'=13 66 | 'rgb'=14 67 | 'rgba'=15 68 | '@roof'=16 69 | 'all'=17 70 | 'item'=18 71 | '.'=19 72 | 'style'=20 73 | 'or'=21 74 | 'and'=22 75 | 'not'=23 76 | 'in'=24 77 | '@'=31 78 | '{'=32 79 | '}'=33 80 | '('=34 81 | ')'=35 82 | '['=36 83 | ']'=37 84 | '|'=38 85 | ','=39 86 | ':'=40 87 | ';'=41 88 | '+'=42 89 | '-'=43 90 | '*'=44 91 | '/'=45 92 | '>'=46 93 | '>='=47 94 | '<'=48 95 | '<='=49 96 | '=='=50 97 | -------------------------------------------------------------------------------- /pml/pml_grammar/pmlLexer.tokens: -------------------------------------------------------------------------------- 1 | T__0=1 2 | T__1=2 3 | T__2=3 4 | T__3=4 5 | T__4=5 6 | T__5=6 7 | T__6=7 8 | T__7=8 9 | T__8=9 10 | T__9=10 11 | T__10=11 12 | T__11=12 13 | T__12=13 14 | T__13=14 15 | T__14=15 16 | T__15=16 17 | T__16=17 18 | T__17=18 19 | T__18=19 20 | T__19=20 21 | OR=21 22 | AND=22 23 | NOT=23 24 | IN=24 25 | IDENTIFIER=25 26 | STRING_LITERAL=26 27 | HEX_NUMBER=27 28 | NUMBER=28 29 | FLOAT=29 30 | INT=30 31 | STRUDEL=31 32 | LCURLY=32 33 | RCURLY=33 34 | LPAREN=34 35 | RPAREN=35 36 | LBRACK=36 37 | RBRACK=37 38 | PIPE=38 39 | COMMA=39 40 | COLON=40 41 | SEMI=41 42 | PLUS=42 43 | MINUS=43 44 | TIMES=44 45 | DIV=45 46 | GT=46 47 | GE=47 48 | LT=48 49 | LE=49 50 | EQ=50 51 | COMMENT=51 52 | WS=52 53 | '@name'=1 54 | 'level'=2 55 | 'symmetry'=3 56 | 'use'=4 57 | 'faces'=5 58 | 'sharpEdges'=6 59 | 'attr'=7 60 | 'bldgAttr'=8 61 | 'random_normal'=9 62 | 'random_weighted'=10 63 | 'if'=11 64 | 'use_from'=12 65 | 'per_building'=13 66 | 'rgb'=14 67 | 'rgba'=15 68 | '@roof'=16 69 | 'all'=17 70 | 'item'=18 71 | '.'=19 72 | 'style'=20 73 | 'or'=21 74 | 'and'=22 75 | 'not'=23 76 | 'in'=24 77 | '@'=31 78 | '{'=32 79 | '}'=33 80 | '('=34 81 | ')'=35 82 | '['=36 83 | ']'=37 84 | '|'=38 85 | ','=39 86 | ':'=40 87 | ';'=41 88 | '+'=42 89 | '-'=43 90 | '*'=44 91 | '/'=45 92 | '>'=46 93 | '>='=47 94 | '<'=48 95 | '<='=49 96 | '=='=50 97 | -------------------------------------------------------------------------------- /realistic/building/roof/mansard.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of Blosm addon for Blender. 3 | Copyright (C) 2014-2018 Vladimir Elistratov 4 | prokitektura+support@gmail.com 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | from . import RoofRealistic 21 | from building.roof.mansard import RoofMansard 22 | from .flat import RoofFlatRealistic 23 | 24 | 25 | class RoofMansardRealistic(RoofRealistic, RoofMansard): 26 | 27 | def renderRoofTextured(self): 28 | if self.makeFlat: 29 | return RoofFlatRealistic.renderRoofTextured(self) 30 | else: 31 | super().renderRoofTextured() 32 | 33 | def renderWalls(self): 34 | RoofFlatRealistic.renderWalls(self) -------------------------------------------------------------------------------- /realistic/building/layer.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of Blosm addon for Blender. 3 | Copyright (C) 2014-2018 Vladimir Elistratov 4 | prokitektura+support@gmail.com 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | import bpy 21 | from building.layer import BuildingLayer 22 | 23 | 24 | class RealisticBuildingLayer(BuildingLayer): 25 | 26 | # the name for the base UV map 27 | uvName = "UVMap" 28 | 29 | # the name for the auxiliary UV map used to keep the size of a BMFace 30 | uvNameSize = "size" 31 | 32 | def prepare(self, instance): 33 | uv_layers = instance.obj.data.uv_layers 34 | uv_layers.new(name=self.uvName) 35 | uv_layers.new(name=self.uvNameSize) 36 | super().prepare(instance) -------------------------------------------------------------------------------- /realistic/building/roof/mesh.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of Blosm addon for Blender. 3 | Copyright (C) 2014-2018 Vladimir Elistratov 4 | prokitektura+support@gmail.com 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | from . import RoofRealistic 21 | from ....building.roof.mesh import RoofMesh 22 | from .flat import RoofFlatRealistic 23 | 24 | 25 | class RoofMeshRealistic(RoofRealistic, RoofMesh): 26 | 27 | def __init__(self, mesh): 28 | super().__init__(mesh) 29 | 30 | def setMaterial(self, obj, slot): 31 | mrr = self.mrr 32 | if mrr: 33 | mrr.renderForObject(obj, slot) 34 | else: 35 | super().setMaterial(obj, slot) 36 | 37 | def renderWalls(self): 38 | RoofFlatRealistic.renderWalls(self) -------------------------------------------------------------------------------- /overlay/arcgis.py: -------------------------------------------------------------------------------- 1 | import os 2 | import bpy 3 | from . import Overlay 4 | 5 | 6 | def getArcgisAccessToken(addonName): 7 | prefs = bpy.context.preferences.addons 8 | if addonName in prefs: 9 | accessToken = prefs[addonName].preferences.arcgisAccessToken 10 | if not accessToken: 11 | raise Exception("An access token for ArcGIS overlays isn't set in the addon preferences") 12 | else: 13 | j = os.path.join 14 | with open(j( j(os.path.realpath(__file__), os.pardir), "arcgis_access_token.txt"), 'r') as file: 15 | accessToken = file.read() 16 | return accessToken 17 | 18 | 19 | class Arcgis(Overlay): 20 | 21 | baseUrl = "https://ibasemaps-api.arcgis.com/arcgis/rest/services/%s/MapServer/tile/{z}/{y}/{x}?token=%s" 22 | 23 | def __init__(self, mapId, maxZoom, addonName): 24 | super().__init__( 25 | self.baseUrl % (mapId, getArcgisAccessToken(addonName)), 26 | maxZoom, 27 | addonName 28 | ) 29 | 30 | def removeAccessToken(self, url): 31 | accessTokenPosition = url.find("?token=") 32 | if accessTokenPosition != -1: 33 | # Remove the access token to decrease the length of the path. 34 | # Windows and probably the other OS have a limit for the path in the file system. 35 | url = url[0:accessTokenPosition] 36 | return url -------------------------------------------------------------------------------- /item/facade.py: -------------------------------------------------------------------------------- 1 | from .div import Div 2 | 3 | 4 | class Facade(Div): 5 | """ 6 | Represents a building facade. 7 | It's typically composed of one or more faces (in the most cases rectangular ones) 8 | """ 9 | 10 | def __init__(self): 11 | super().__init__() 12 | 13 | self.buildingPart = "facade" 14 | 15 | self.outer = True 16 | 17 | self.front = False 18 | self.back = False 19 | self.side = False 20 | 21 | self.numEntrances = 0 22 | 23 | def init(self): 24 | super().init() 25 | 26 | if not self.outer: 27 | self.outer = True 28 | 29 | if self.front: 30 | self.front = False 31 | elif self.back: 32 | self.back = False 33 | elif self.side: 34 | self.side = False 35 | 36 | if self.numEntrances: 37 | self.numEntrances = 0 38 | 39 | @classmethod 40 | def getItem(cls, volumeGenerator, parent, indices, edgeIndex): 41 | item = volumeGenerator.itemFactory.getItem(cls) 42 | item.init() 43 | item.parent = parent 44 | item.footprint = parent 45 | item.building = parent.building 46 | item.indices = indices 47 | item.edgeIndex = edgeIndex 48 | # knows which geometry the facade items have and how to map UV-coordinates 49 | volumeGenerator.initFacadeItem(item) 50 | return item -------------------------------------------------------------------------------- /threed_tiles/py3dtiles/tileset/utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | 4 | from .content import Pnts, read_binary_tile_content 5 | 6 | 7 | def number_of_points_in_tileset(tileset_path: Path) -> int: 8 | with tileset_path.open() as f: 9 | tileset = json.load(f) 10 | 11 | nb_points = 0 12 | 13 | children_tileset_info = [(tileset["root"], tileset["root"]["refine"])] 14 | while children_tileset_info: 15 | child_tileset, parent_refine = children_tileset_info.pop() 16 | child_refine = ( 17 | child_tileset["refine"] if child_tileset.get("refine") else parent_refine 18 | ) 19 | 20 | if "content" in child_tileset: 21 | content = tileset_path.parent / child_tileset["content"]["uri"] 22 | 23 | pnts_should_count = "children" not in child_tileset or child_refine == "ADD" 24 | if content.suffix == ".pnts" and pnts_should_count: 25 | tile = read_binary_tile_content(content) 26 | if isinstance(tile, Pnts): 27 | nb_points += tile.body.feature_table.nb_points() 28 | elif content.suffix == ".json": 29 | with content.open() as f: 30 | sub_tileset = json.load(f) 31 | children_tileset_info.append((sub_tileset["root"], child_refine)) 32 | 33 | if "children" in child_tileset: 34 | children_tileset_info += [ 35 | (sub_child_tileset, child_refine) 36 | for sub_child_tileset in child_tileset["children"] 37 | ] 38 | 39 | return nb_points 40 | -------------------------------------------------------------------------------- /parse/osm/relation/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of Blosm addon for Blender. 3 | Copyright (C) 2014-2018 Vladimir Elistratov 4 | prokitektura+support@gmail.com 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | class Relation: 21 | """ 22 | A class to represent an OSM relation 23 | 24 | Some attributes: 25 | l (app.Layer): layer index used to place the related geometry to a specific Blender object 26 | tags (dict): OSM tags 27 | m: A manager used during the rendering, if None applies defaults 28 | during the rendering 29 | r (bool): Defines if we need to render (True) or not (False) the OSM relation 30 | in the function 31 | rr: A special renderer for the OSM relation 32 | """ 33 | 34 | # use __slots__ for memory optimization 35 | __slots__ = ("l", "tags", "m", "r", "rr", "valid") 36 | 37 | def __init__(self): 38 | self.r = False 39 | self.rr = None 40 | self.valid = True -------------------------------------------------------------------------------- /item_renderer/texture/export/level.py: -------------------------------------------------------------------------------- 1 | from .container import Container 2 | from ..level import CurtainWall as CurtainWallBase 3 | from ...util import getPath 4 | 5 | 6 | class CurtainWall(CurtainWallBase, Container): 7 | 8 | def __init__(self): 9 | # a reference to the Container class used in the parent classes 10 | self.Container = Container 11 | Container.__init__(self, exportMaterials=True) 12 | CurtainWallBase.__init__(self) 13 | 14 | def makeTexture(self, item, textureFilename, textureDir, textureFilepath, color, facadeTextureInfo, claddingTextureInfo, uvs): 15 | textureExporter = self.r.textureExporter 16 | scene = textureExporter.getTemplateScene("compositing_facade_specular_color") 17 | nodes = textureExporter.makeCommonPreparations( 18 | scene, 19 | textureFilename, 20 | textureDir 21 | ) 22 | # facade texture 23 | textureExporter.setImage( 24 | facadeTextureInfo["name"], 25 | getPath(self.r, facadeTextureInfo["path"]), 26 | nodes, 27 | "facade_texture" 28 | ) 29 | specularMapName = facadeTextureInfo.get("specularMapName") 30 | if specularMapName: 31 | textureExporter.setImage( 32 | specularMapName, 33 | getPath(self.r, facadeTextureInfo["path"]), 34 | nodes, 35 | "specular_map" 36 | ) 37 | # cladding color 38 | textureExporter.setColor(color, nodes, "cladding_color") 39 | # render the resulting texture 40 | textureExporter.renderTexture(scene, textureFilepath) -------------------------------------------------------------------------------- /item_renderer/test/__init__.py: -------------------------------------------------------------------------------- 1 | from parse.osm.way import Way 2 | 3 | 4 | class GeometryRenderer: 5 | 6 | def init(self, itemRenderers, globalRenderer): 7 | self.itemRenderers = itemRenderers 8 | self.r = globalRenderer 9 | 10 | def writeLog(self, roofItem, message): 11 | self.r.app.log.write("%s|%s|%s\n" % 12 | ( 13 | roofItem.building.outline.tags["id"], 14 | "way" if isinstance(roofItem.building.outline, Way) else "relation", 15 | message 16 | ) 17 | ) 18 | 19 | def render(self, roofItem): 20 | self.writeLog(roofItem, "Not hipped roof") 21 | 22 | 23 | class GeometryRendererRoofFlat(GeometryRenderer): 24 | 25 | def render(self, roofItem): 26 | self.writeLog(roofItem, "Flat roof") 27 | 28 | 29 | class GeometryRendererRoofWithSides(GeometryRenderer): 30 | 31 | def render(self, roofItem): 32 | if roofItem.exception: 33 | self.writeLog(roofItem, roofItem.exception) 34 | return 35 | # check for faces consisting of less than 3 vertices 36 | for roofSide in roofItem.roofSides: 37 | if len(roofSide.indices) < 3: 38 | self.writeLog(roofItem, "Less than 3 verts") 39 | break 40 | for roofSide in roofItem.roofSides: 41 | if len(roofSide.indices) != len(set(roofSide.indices)): 42 | self.writeLog(roofItem, "Duplicated indices") 43 | break 44 | #self.writeLog(roofItem, "Ok") 45 | 46 | 47 | class GeometryRendererFacade(GeometryRenderer): 48 | 49 | def render(self, footprint, data): 50 | return -------------------------------------------------------------------------------- /realistic/building/roof/hipped.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of Blosm addon for Blender. 3 | Copyright (C) 2014-2018 Vladimir Elistratov 4 | prokitektura+support@gmail.com 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | from . import RoofRealistic 21 | from building.roof.hipped import RoofHipped 22 | from .flat import RoofFlatRealistic, RoofFlat 23 | from .profile import RoofProfileRealistic, RoofProfile 24 | 25 | 26 | class RoofHippedRealistic(RoofRealistic, RoofHipped): 27 | 28 | def renderRoofTextured(self): 29 | if self.makeFlat: 30 | return RoofFlatRealistic.renderRoofTextured(self) 31 | else: 32 | super().renderRoofTextured() 33 | 34 | def renderWalls(self): 35 | if self.makeFlat: 36 | if self.mrw: 37 | RoofFlatRealistic.renderWalls(self) 38 | else: 39 | RoofFlat.renderWalls(self) 40 | else: 41 | if self.mrw: 42 | RoofProfileRealistic.renderWalls(self) 43 | else: 44 | RoofProfile.renderWalls(self) -------------------------------------------------------------------------------- /pml/antlr4/tree/TokenTagToken.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2012-2017 The ANTLR Project. All rights reserved. 3 | # Use of this file is governed by the BSD 3-clause license that 4 | # can be found in the LICENSE.txt file in the project root. 5 | # 6 | 7 | # 8 | # A {@link Token} object representing a token of a particular type; e.g., 9 | # {@code }. These tokens are created for {@link TagChunk} chunks where the 10 | # tag corresponds to a lexer rule or token type. 11 | # 12 | from antlr4.Token import CommonToken 13 | 14 | 15 | class TokenTagToken(CommonToken): 16 | 17 | # Constructs a new instance of {@link TokenTagToken} with the specified 18 | # token name, type, and label. 19 | # 20 | # @param tokenName The token name. 21 | # @param type The token type. 22 | # @param label The label associated with the token tag, or {@code null} if 23 | # the token tag is unlabeled. 24 | # 25 | def __init__(self, tokenName:str, type:int, label:str=None): 26 | super().__init__(type=type) 27 | self.tokenName = tokenName 28 | self.label = label 29 | self._text = self.getText() 30 | 31 | # 32 | # {@inheritDoc} 33 | # 34 | #

The implementation for {@link TokenTagToken} returns the token tag 35 | # formatted with {@code <} and {@code >} delimiters.

36 | # 37 | def getText(self): 38 | if self.label is None: 39 | return "<" + self.tokenName + ">" 40 | else: 41 | return "<" + self.label + ":" + self.tokenName + ">" 42 | 43 | #

The implementation for {@link TokenTagToken} returns a string of the form 44 | # {@code tokenName:type}.

45 | # 46 | def __str__(self): 47 | return self.tokenName + ":" + str(self.type) 48 | -------------------------------------------------------------------------------- /script/emission_for_windows.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of Blosm addon for Blender. 3 | Copyright (C) 2014-2018 Vladimir Elistratov 4 | prokitektura+support@gmail.com 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | import random 21 | import numpy 22 | import bpy 23 | 24 | numWindows = 200 25 | numProbabilities = 100 26 | imageName = "emission_for_windows" 27 | 28 | imageData = numpy.zeros(4*numWindows*numProbabilities) 29 | 30 | for y in range(numProbabilities): 31 | for x in range(numWindows): 32 | offset = 4*(y*numWindows+x) 33 | randomNumber = random.randrange(numProbabilities-1) 34 | value = 1. if randomNumber < y else 0. 35 | imageData[offset:offset+4] = value, value, value, 1. 36 | 37 | 38 | images = bpy.data.images 39 | 40 | if imageName in images: 41 | images.remove(images[imageName], True) 42 | 43 | 44 | image = bpy.data.images.new( 45 | imageName, 46 | width = numWindows, 47 | height = numProbabilities, 48 | alpha=False, 49 | float_buffer=False 50 | ) 51 | image.pixels = imageData 52 | image.use_fake_user = True 53 | # pack the image into .blend file 54 | image.pack(as_png=True) -------------------------------------------------------------------------------- /threed_tiles/py3dtiles/tileset/root_property.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from abc import ABC, abstractmethod 4 | from typing import TYPE_CHECKING, Generic, TypeVar 5 | 6 | from py3dtiles.typing import ExtraDictType, RootPropertyDictType 7 | 8 | if TYPE_CHECKING: 9 | from typing_extensions import Self 10 | 11 | from py3dtiles.tileset.extension import BaseExtension 12 | 13 | _JsonDictT = TypeVar("_JsonDictT", bound=RootPropertyDictType) 14 | 15 | 16 | class RootProperty(ABC, Generic[_JsonDictT]): 17 | """ 18 | One the 3DTiles notions defined as an abstract data model through 19 | a schema of the 3DTiles specifications (either core of extensions). 20 | """ 21 | 22 | def __init__(self) -> None: 23 | self.extensions: dict[str, BaseExtension] = {} 24 | self.extras: ExtraDictType = {} 25 | 26 | @classmethod 27 | @abstractmethod 28 | def from_dict(cls, data_dict: _JsonDictT) -> Self: 29 | ... 30 | 31 | @abstractmethod 32 | def to_dict(self) -> _JsonDictT: 33 | ... 34 | 35 | def add_root_properties_to_dict(self, dict_data: _JsonDictT) -> _JsonDictT: 36 | # we cannot merge root_property_data without mypy issues 37 | if self.extensions: 38 | dict_data["extensions"] = { 39 | name: extension.to_dict() for name, extension in self.extensions.items() 40 | } 41 | 42 | if self.extras: 43 | dict_data["extras"] = self.extras 44 | 45 | return dict_data 46 | 47 | def set_properties_from_dict( 48 | self, 49 | dict_data: _JsonDictT, 50 | ) -> None: 51 | self.extensions = {} # TODO not yet implemented 52 | if "extras" in dict_data: 53 | self.extras = dict_data["extras"] 54 | -------------------------------------------------------------------------------- /threed_tiles/py3dtiles/tileset/content/feature_table.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from abc import abstractmethod 4 | from typing import TYPE_CHECKING, Any, Generic, TypeVar, Union 5 | 6 | import numpy as np 7 | import numpy.typing as npt 8 | 9 | if TYPE_CHECKING: 10 | from .tile_content import TileContentHeader 11 | 12 | ComponentNumpyType = Union[ 13 | np.int8, np.uint8, np.int16, np.uint16, np.int32, np.uint32, np.float32, np.float64 14 | ] 15 | 16 | 17 | class FeatureTableHeader: 18 | @abstractmethod 19 | def to_array(self) -> npt.NDArray[np.uint8]: 20 | ... 21 | 22 | 23 | class FeatureTableBody: 24 | def __init__(self) -> None: 25 | self.data: list[npt.NDArray[ComponentNumpyType]] = [] 26 | 27 | @abstractmethod 28 | def to_array(self) -> npt.NDArray[np.uint8]: 29 | ... 30 | 31 | @property 32 | def nbytes(self) -> int: 33 | return sum([data.nbytes for data in self.data]) 34 | 35 | 36 | _FeatureTableHeaderT = TypeVar("_FeatureTableHeaderT", bound=FeatureTableHeader) 37 | _FeatureTableBodyT = TypeVar("_FeatureTableBodyT", bound=FeatureTableBody) 38 | 39 | 40 | class FeatureTable(Generic[_FeatureTableHeaderT, _FeatureTableBodyT]): 41 | """ 42 | Only the JSON header has been implemented for now. According to the feature 43 | table documentation, the binary body is useful for storing long arrays of 44 | data (better performances) 45 | """ 46 | 47 | header: _FeatureTableHeaderT 48 | body: _FeatureTableBodyT 49 | 50 | @abstractmethod 51 | def to_array(self) -> npt.NDArray[np.uint8]: 52 | ... 53 | 54 | @staticmethod 55 | @abstractmethod 56 | def from_array( 57 | tile_header: TileContentHeader, array: npt.NDArray[np.uint8] 58 | ) -> FeatureTable[Any, Any]: 59 | ... 60 | -------------------------------------------------------------------------------- /overlay/mapbox.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of Blosm addon for Blender. 3 | Copyright (C) 2014-2018 Vladimir Elistratov 4 | prokitektura+support@gmail.com 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | from . import Overlay, getMapboxAccessToken 21 | 22 | 23 | class Mapbox(Overlay): 24 | 25 | baseUrl = "http://[a,b,c,d].tiles.mapbox.com/v4/%s/{z}/{x}/{y}.png?access_token=%s" 26 | 27 | def __init__(self, mapId, maxZoom, addonName): 28 | super().__init__( 29 | self.baseUrl % (mapId, getMapboxAccessToken(addonName)), 30 | maxZoom, 31 | addonName 32 | ) 33 | self.mapId = mapId 34 | if mapId == "mapbox.satellite": 35 | self.imageExtension = "jpg" 36 | 37 | def getOverlaySubDir(self): 38 | return self.mapId 39 | 40 | def removeAccessToken(self, url): 41 | accessTokenPosition = url.find("?access_token=") 42 | if accessTokenPosition != -1: 43 | # Remove the access token to decrease the length of the path. 44 | # Windows and probably the other OS have a limit for the path in the file system. 45 | url = url[0:accessTokenPosition] 46 | return url -------------------------------------------------------------------------------- /pml/examples/place_of_worship.pml: -------------------------------------------------------------------------------- 1 | //=================================================== 2 | // PML file for Blender-OSM 3 | // Styles for "Place of worship" tag buildings 4 | //=================================================== 5 | @name "place of worship"; 6 | 7 | footprint { 8 | height: attr("height"); 9 | minHeight: attr("min_height"); 10 | //numLevels: 0; 11 | topHeight: 0.; 12 | roofShape: attr("roof:shape") | flat; 13 | roofHeight: attr("roof:height"); 14 | roofOrientation: attr("roof:orientation"); 15 | buildingPart: attr("building:part"); 16 | claddingMaterial: per_building( 17 | attr("building:material") | plaster 18 | ); 19 | 20 | } 21 | 22 | //Cube -- main volume 23 | facade 24 | [item.footprint["buildingPart"] == "cube"] 25 | { 26 | claddingColor: red; 27 | markup: [ 28 | level{} 29 | ] 30 | } 31 | 32 | //refectory 33 | facade 34 | [item.footprint["buildingPart"] == "refectory"] 35 | { 36 | claddingColor: green; 37 | } 38 | 39 | // porch 40 | facade 41 | [item.footprint["buildingPart"] == "porch"] 42 | { 43 | claddingColor: blue; 44 | } 45 | 46 | // belltower 47 | facade 48 | [item.footprint["buildingPart"] == "belltower"] 49 | { 50 | claddingColor: blue; 51 | } 52 | 53 | //apse 54 | facade 55 | [item.footprint["buildingPart"] == "apse"] 56 | { 57 | claddingColor: blue; 58 | } 59 | 60 | facade 61 | [item.footprint["buildingPart"] == "tholobate"] 62 | { 63 | claddingColor: blue; 64 | } 65 | 66 | //Unknown facade 67 | facade 68 | { 69 | //absolutely nothing 70 | } 71 | 72 | 73 | roof { 74 | roofCladdingMaterial: attr("roof:material") | metal; 75 | roofCladdingColor: 76 | attr("roof:colour") 77 | | 78 | // roofCladdingMaterial == "metal" 79 | random_weighted( 80 | (#afafaf, 1), 81 | (#b2b2a6, 1), 82 | (#c8c2b6, 1) 83 | ) 84 | ; 85 | faces: if (item.footprint["roofShape"] in ("dome", "onion")) smooth; 86 | } 87 | 88 | 89 | -------------------------------------------------------------------------------- /util/debug/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | from string import Template 3 | import bpy 4 | 5 | from app.blender import app 6 | 7 | 8 | def getTemplate(): 9 | with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), bpy.context.scene["outputTemplate"]), 'r') as file: 10 | template = file.read() 11 | return template 12 | 13 | 14 | def getDebugHippedRoofPath(): 15 | return bpy.context.scene["outputDir"] 16 | 17 | 18 | class TemplateBpypolyskel(Template): 19 | 20 | def substitute(self, *args, **kws): 21 | # iterate through to find Python lists or tuples to get a string out of their elements 22 | for k in kws: 23 | value = kws[k] 24 | if isinstance(value, (list, tuple)): 25 | # replace 26 | value = "[\n %s\n]" % ",\n ".join(repr(element) for element in value) 27 | kws[k] = value 28 | 29 | return super().substitute(*args, **kws) 30 | 31 | 32 | def dumpInputHippedRoof(verts, firstVertIndex, numPolygonVerts, holesInfo, unitVectors): 33 | """ 34 | Creates a Python script with the automated tests out of the template . 35 | The resulting file is saved to directory 36 | """ 37 | with open( 38 | os.path.join( 39 | getDebugHippedRoofPath(), 40 | "%s%s.py" % ( 41 | bpy.context.scene["outputFileNamePrefix"], 42 | os.path.splitext(os.path.basename(app.osmFilepath))[0] 43 | ) 44 | ), 45 | 'w') as file: 46 | file.write( 47 | TemplateBpypolyskel( getTemplate() ).substitute( 48 | verts = verts, 49 | unitVectors = unitVectors, 50 | numPolygonVerts = numPolygonVerts, 51 | firstVertIndex = firstVertIndex, 52 | holesInfo = holesInfo 53 | ) 54 | ) -------------------------------------------------------------------------------- /pml/__init__.py: -------------------------------------------------------------------------------- 1 | from antlr4 import InputStream, CommonTokenStream, ParseTreeWalker 2 | from .pml_grammar.pmlLexer import pmlLexer 3 | from .pml_grammar.pmlParser import pmlParser 4 | from .PythonListener import PythonListener 5 | from .PML_Preprocessor import PML_Preprocessor 6 | from .ExceptionManagement import ParserExceptionListener 7 | from .ExceptionManagement import ParserException 8 | 9 | 10 | class PML: 11 | 12 | def __init__(self, pmlFilePath, rootDir): 13 | """ 14 | Args: 15 | pmlFilePath (str): Path to a PML file 16 | rootDir (str): Root directory to use for includes <@include> 17 | if the path in <@include> starts with '/' 18 | """ 19 | self.pmlFilePath = pmlFilePath 20 | self.rootDir = rootDir 21 | 22 | def getPythonCode(self): 23 | preprocessor = PML_Preprocessor(self.rootDir) 24 | preprocessor.process(self.pmlFilePath) 25 | 26 | inputStream = InputStream(preprocessor.getStream()) 27 | lexer = pmlLexer(inputStream) 28 | stream = CommonTokenStream(lexer) 29 | parser = pmlParser(stream) 30 | 31 | parser.removeErrorListeners() 32 | exceptionListener = ParserExceptionListener() 33 | parser.addErrorListener(exceptionListener) 34 | 35 | try: 36 | tree = parser.styles() 37 | except ParserException as e: 38 | line, col, msg = e.errParams() 39 | localFile, localLine = preprocessor.trackDownLineNr(line) 40 | raise Exception( 41 | "Error in file {file} on line {line}, col {col}: {msg}".format( 42 | file = localFile, line = localLine, col = col, msg = msg 43 | ) 44 | ) 45 | 46 | translator = PythonListener() 47 | walker = ParseTreeWalker() 48 | walker.walk(translator, tree) 49 | 50 | return translator.getCode() -------------------------------------------------------------------------------- /pml/examples/class_only.pml: -------------------------------------------------------------------------------- 1 | @name "single family house"; 2 | 3 | @meta{ 4 | buildingUse: single_family; 5 | } 6 | 7 | footprint{ 8 | roofShape: gabled; 9 | numLevels: 1; 10 | numRoofLevels: 1; 11 | levelHeight: random_normal(3.); 12 | } 13 | 14 | facade[item.front] { 15 | class: facade_front; 16 | } 17 | 18 | facade[item.back] { 19 | class: facade_back; 20 | } 21 | 22 | facade { 23 | class: facade_side; 24 | } 25 | 26 | roof{ 27 | class: roof; 28 | roofCladdingMaterial: metal; 29 | roofCladdingColor: darkgray; 30 | } 31 | 32 | 33 | @name "commercial"; 34 | 35 | @meta{ 36 | buildingUse: office; 37 | } 38 | 39 | footprint{ 40 | roofShape: flat; 41 | numLevels: attr("building:levels") | random_weighted( (2,2), (3,2), (4,3), (5,1) ); 42 | levelHeight: random_normal(3.); 43 | } 44 | 45 | // 46 | // 2 floors 47 | // 48 | 49 | facade[item.front and item.footprint["numLevels"]==2] { 50 | class: facade_front_2floors; 51 | } 52 | 53 | facade[item.footprint["numLevels"]==2] { 54 | class: facade_side_2floors; 55 | } 56 | 57 | 58 | // 59 | // 3 floors 60 | // 61 | 62 | facade[item.front and item.footprint["numLevels"]==3] { 63 | class: facade_front_3floors; 64 | } 65 | 66 | facade[item.footprint["numLevels"]==3] { 67 | class: facade_side_3floors; 68 | } 69 | 70 | 71 | // 72 | // 4 floors 73 | // 74 | 75 | facade[item.front and item.footprint["numLevels"]==4] { 76 | class: facade_front_4floors; 77 | } 78 | 79 | facade[item.footprint["numLevels"]==4] { 80 | class: facade_side_4floors; 81 | } 82 | 83 | 84 | // 85 | // 5 floors 86 | // 87 | 88 | facade[item.front and item.footprint["numLevels"]==5] { 89 | class: facade_front_5floors; 90 | } 91 | 92 | facade[item.footprint["numLevels"]==5] { 93 | class: facade_side_5floors; 94 | } 95 | 96 | 97 | roof{ 98 | class: roof; 99 | roofCladdingMaterial: concrete; 100 | roofCladdingColor: gray; 101 | } -------------------------------------------------------------------------------- /script/command_line.py: -------------------------------------------------------------------------------- 1 | from parse.osm import Osm 2 | from app.command_line import CommandLineApp 3 | 4 | 5 | def importOsm(): 6 | 7 | a = CommandLineApp() 8 | 9 | #try: 10 | a.initOsm() 11 | #except Exception as e: 12 | # print("Error") 13 | # return 14 | 15 | forceExtentCalculation = bool(a.osmFilepath) 16 | 17 | setupScript = a.setupScript 18 | if setupScript: 19 | setup_function = a.loadSetupScript(setupScript) 20 | if not setup_function: 21 | return 22 | else: 23 | from setup.base import setup as setup_function 24 | 25 | osm = Osm(a) 26 | 27 | setup_function(a, osm) 28 | 29 | # additional command line arguments may have introduced in 30 | a.parseArgs() 31 | 32 | a.createLayers(osm) 33 | 34 | if not a.osmFilepath and a.coords: 35 | osm.setProjection( (a.minLat+a.maxLat)/2., (a.minLon+a.maxLon)/2. ) 36 | 37 | osm.parse(a.osmFilepath, forceExtentCalculation=forceExtentCalculation) 38 | if a.loadMissingMembers and a.incompleteRelations: 39 | try: 40 | a.loadMissingWays(osm) 41 | except Exception as e: 42 | print(str(e)) 43 | a.loadMissingMembers = False 44 | a.processIncompleteRelations(osm) 45 | if not osm.projection: 46 | # wasn't set so far if there were only incomplete relations that 47 | # satisfy . 48 | # See also the comments in 49 | # at the end of the method 50 | osm.setProjection( (osm.minLat+osm.maxLat)/2., (osm.minLon+osm.maxLon)/2. ) 51 | 52 | if forceExtentCalculation: 53 | a.minLat = osm.minLat 54 | a.maxLat = osm.maxLat 55 | a.minLon = osm.minLon 56 | a.maxLon = osm.maxLon 57 | 58 | a.initLayers() 59 | 60 | a.process() 61 | a.render() 62 | 63 | a.clean() 64 | 65 | 66 | importOsm() -------------------------------------------------------------------------------- /item/level.py: -------------------------------------------------------------------------------- 1 | from .container import Container 2 | 3 | 4 | class Level(Container): 5 | 6 | def __init__(self): 7 | super().__init__() 8 | self.buildingPart = "level" 9 | 10 | def getLevelRenderer(self, levelGroup, itemRenderers): 11 | """ 12 | Get a renderer for the representing the item. 13 | """ 14 | # here is the special case: the door which the only item in the markup 15 | return itemRenderers["Door"]\ 16 | if len(self.markup) == 1 and self.markup[0].__class__.__name__ == "Door"\ 17 | else itemRenderers["Level"] 18 | 19 | @classmethod 20 | def getItem(cls, itemFactory, parent, styleBlock): 21 | item = itemFactory.getItem(cls) 22 | item.init() 23 | item.parent = parent 24 | item.footprint = parent.footprint 25 | item.building = parent.building 26 | item.styleBlock = styleBlock 27 | return item 28 | 29 | 30 | class CurtainWall(Level): 31 | 32 | width = 0. 33 | 34 | def __init__(self): 35 | super().__init__() 36 | self.buildingPart = "curtain_wall" 37 | # It doesn't need the 38 | self.hasFacadePatternInfo = False 39 | 40 | def getLevelRenderer(self, levelGroup, itemRenderers): 41 | """ 42 | Get a renderer for the representing the item. 43 | """ 44 | return itemRenderers["CurtainWall"] 45 | 46 | def getWidth(self): 47 | width = self.getStyleBlockAttr("width") 48 | if width is None: 49 | width = CurtainWall.width 50 | self.width = width 51 | return width 52 | 53 | @classmethod 54 | def getItem(cls, itemFactory, parent, styleBlock): 55 | item = itemFactory.getItem(cls) 56 | item.init() 57 | item.parent = parent 58 | item.footprint = parent.footprint 59 | item.building = parent.building 60 | item.styleBlock = styleBlock 61 | return item -------------------------------------------------------------------------------- /threed_tiles/py3dtiles/tileset/bounding_volume.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from abc import abstractmethod 4 | from typing import TYPE_CHECKING, Any, Generic, TypeVar 5 | 6 | import numpy as np 7 | import numpy.typing as npt 8 | 9 | from py3dtiles.typing import ( 10 | BoundingVolumeBoxDictType, 11 | BoundingVolumeRegionDictType, 12 | BoundingVolumeSphereDictType, 13 | ) 14 | 15 | from .root_property import RootProperty 16 | 17 | if TYPE_CHECKING: 18 | from typing_extensions import Self 19 | 20 | from py3dtiles.tileset import Tile 21 | 22 | _BoundingVolumeJsonDictT = TypeVar( 23 | "_BoundingVolumeJsonDictT", 24 | BoundingVolumeBoxDictType, 25 | BoundingVolumeRegionDictType, 26 | BoundingVolumeSphereDictType, 27 | ) 28 | 29 | 30 | class BoundingVolume( 31 | RootProperty[_BoundingVolumeJsonDictT], Generic[_BoundingVolumeJsonDictT] 32 | ): 33 | """ 34 | Abstract class used as interface for box, region and sphere 35 | """ 36 | 37 | def __init__(self) -> None: 38 | super().__init__() 39 | 40 | @classmethod 41 | @abstractmethod 42 | def from_dict(cls, bounding_volume_dict: _BoundingVolumeJsonDictT) -> Self: 43 | ... 44 | 45 | def is_box(self) -> bool: 46 | return False 47 | 48 | def is_region(self) -> bool: 49 | return False 50 | 51 | def is_sphere(self) -> bool: 52 | return False 53 | 54 | @abstractmethod 55 | def get_center(self) -> npt.NDArray[np.float64]: 56 | ... 57 | 58 | @abstractmethod 59 | def translate(self, offset: npt.NDArray[np.float64]) -> None: 60 | ... 61 | 62 | @abstractmethod 63 | def transform(self, transform: npt.NDArray[np.float64]) -> None: 64 | ... 65 | 66 | @abstractmethod 67 | def add(self, other: BoundingVolume[Any]) -> None: 68 | ... 69 | 70 | @abstractmethod 71 | def sync_with_children(self, owner: Tile) -> None: 72 | ... 73 | 74 | @abstractmethod 75 | def to_dict(self) -> _BoundingVolumeJsonDictT: 76 | ... 77 | -------------------------------------------------------------------------------- /pml/PML2PythonTranslator.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | from antlr4 import InputStream, CommonTokenStream, ParseTreeWalker 4 | from pml_grammar.pmlLexer import pmlLexer 5 | from pml_grammar.pmlParser import pmlParser 6 | from PythonListener import PythonListener 7 | from ExceptionManagement import ParserExceptionListener 8 | from PML_Preprocessor import PML_Preprocessor 9 | from ExceptionManagement import ParserException 10 | 11 | def main(argv): 12 | # argv[2] is the path to the folder where the asset packages 13 | # are stored. Do not add '/' at end. 14 | preprocessor = PML_Preprocessor(argv[2]) 15 | hadErrors = False 16 | errorText = '' 17 | try: 18 | preprocessor.process(argv[1]) 19 | except Exception as e: 20 | errorText = str(e) 21 | hadErrors = True 22 | 23 | if not hadErrors: 24 | input_stream = InputStream(preprocessor.getStream()) 25 | lexer = pmlLexer(input_stream) 26 | stream = CommonTokenStream(lexer) 27 | parser = pmlParser(stream) 28 | 29 | parser.removeErrorListeners() 30 | exceptionListener = ParserExceptionListener() 31 | parser.addErrorListener( exceptionListener ) 32 | 33 | try: 34 | tree = parser.styles() 35 | except ParserException as e: 36 | line,col,msg = e.errParams() 37 | localFile,localLine = preprocessor.trackDownLineNr(line) 38 | errorText = 'Error in file {file} on line {line}, col {col}: {msg}'.format( 39 | file = localFile, line = localLine, col = col, msg = msg) 40 | hadErrors = True 41 | except Exception as e: 42 | errorText = str(e) 43 | hadErrors = True 44 | 45 | if not hadErrors: 46 | translator = PythonListener() 47 | walker = ParseTreeWalker() 48 | walker.walk(translator, tree) 49 | sys.stdout.write( translator.getCode() ) 50 | 51 | if hadErrors: 52 | sys.stdout.write(errorText) 53 | 54 | if __name__ == '__main__': 55 | main(sys.argv) -------------------------------------------------------------------------------- /grammar/library.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Library: 4 | """ 5 | Library of style blocks with and without a namespace 6 | """ 7 | 8 | def __init__(self): 9 | # the current style id 10 | self.styleId = -1 11 | # a place holder for style blocks without a namespace 12 | self.library = [] 13 | self.numEntries = 0 14 | # a place holder for stytle blocks with a namespace 15 | self.libraryNS = {} 16 | 17 | def getStyleId(self): 18 | self.styleId += 1 19 | self.numEntries += 1 20 | self.library.append({}) 21 | return self.styleId 22 | 23 | def addStyleBlock(self, defName, styleBlock, styleId): 24 | libraryEntry = self.library[styleId] 25 | className = styleBlock.__class__.__name__ 26 | if not className in libraryEntry: 27 | libraryEntry[className] = {} 28 | libraryEntry[className][defName] = styleBlock 29 | 30 | def getStyleBlock(self, defName, styleBlockType, styleId): 31 | if styleId > self.numEntries: 32 | return None 33 | libraryEntry = self.library[styleId].get(styleBlockType) 34 | if not libraryEntry: 35 | return None 36 | return libraryEntry.get(defName) 37 | 38 | def addStyleBlockNS(self, defName, styleBlock, namespace): 39 | libraryEntry = self.libraryNS.get(namespace) 40 | if not libraryEntry: 41 | libraryEntry = {} 42 | self.libraryNS[namespace] = libraryEntry 43 | className = styleBlock.__class__.__name__ 44 | if not className in libraryEntry: 45 | libraryEntry[className] = {} 46 | libraryEntry[className][defName] = styleBlock 47 | 48 | def getStyleBlockNS(self, defName, styleBlockType, namespace): 49 | if not namespace in self.libraryNS: 50 | return None 51 | libraryEntry = self.libraryNS[namespace].get(styleBlockType) 52 | if not libraryEntry: 53 | return None 54 | return libraryEntry.get(defName) 55 | 56 | 57 | library = Library() -------------------------------------------------------------------------------- /style/__init__.py: -------------------------------------------------------------------------------- 1 | import os, math 2 | 3 | from grammar import * 4 | from grammar.scope import PerBuilding, PerFootprint 5 | from grammar import units, symmetry, smoothness 6 | from grammar.value import Value, FromAttr, FromBldgAttr, Alternatives, Conditional, FromStyleBlockAttr, Constant 7 | from grammar.value import RandomWeighted, RandomNormal 8 | from action.volume.roof import Roof as RoofDefs 9 | from item.defs import * 10 | 11 | from pml import PML 12 | 13 | 14 | minHeightForLevels = 1.5 15 | minWidthForOpenings = 1. 16 | 17 | 18 | class StyleStore: 19 | 20 | def __init__(self, app, styles=None): 21 | # None is returned by getStyle(..) in a setup script if the building is to be skipped for rendering 22 | self.styles = {None: None} 23 | # overwrite an entry with the given key in if the key already exists in 24 | self.overwrite = True 25 | 26 | if styles: 27 | self.addStyles(styles) 28 | elif app.pmlFilepath: 29 | self.loadFromFile(app.pmlFilepath, app.assetsDir) 30 | 31 | def addStyles(self, styles): 32 | for styleName in styles: 33 | self.add(styleName, styles[styleName]) 34 | 35 | def add(self, styleName, style): 36 | style = Grammar(style) 37 | self.styles[styleName] = style 38 | 39 | def get(self, styleName): 40 | return self.styles[styleName] 41 | 42 | def loadFromFiles(self, files): 43 | for file in files: 44 | if os.path.isfile(file) and file.lower().endswith(".pml"): 45 | self.loadFromFile(file) 46 | 47 | def loadFromFile(self, file, assetsDir): 48 | _locals = {} 49 | exec(PML(file, assetsDir).getPythonCode(), None, _locals) 50 | styles = _locals["styles"] 51 | if isinstance(styles, dict): 52 | self.addStyles(styles) 53 | else: # a Python list 54 | # use the file name without the extension as the style name 55 | styleName = os.path.splitext(os.path.basename(file))[0] 56 | self.add(styleName, styles) -------------------------------------------------------------------------------- /threed_tiles/py3dtiles/exceptions.py: -------------------------------------------------------------------------------- 1 | class Py3dtilesException(Exception): 2 | """ 3 | All exceptions thrown by py3dtiles code derives this class. 4 | 5 | Client code that wishes to catch all py3dtiles exception can use `except Py3dtilesException`. 6 | """ 7 | 8 | 9 | class FormatSupportMissingException(Py3dtilesException): 10 | """ 11 | This exception is thrown when the user attempts to convert a file in an unsupported format. 12 | The format can be unsupported either because support is not implemented, or because a dependency is missing. 13 | """ 14 | 15 | 16 | class TilerException(Py3dtilesException): 17 | """ 18 | This exception will be thrown when there is an issue during a tiling task. 19 | """ 20 | 21 | 22 | class WorkerException(TilerException): 23 | """ 24 | This exception will be thrown by the conversion code if one exception occurs inside a worker. 25 | """ 26 | 27 | 28 | class SrsInMissingException(Py3dtilesException): 29 | """ 30 | This exception will be thrown when an input srs is required but not provided. 31 | """ 32 | 33 | 34 | class SrsInMixinException(Py3dtilesException): 35 | """ 36 | This exception will be thrown when among all input files, there is a mix of input srs. 37 | """ 38 | 39 | 40 | class Invalid3dtilesError(Py3dtilesException): 41 | """ 42 | This exception will be thrown if the 3d tile specification isn't respected. 43 | """ 44 | 45 | 46 | class InvalidPntsError(Invalid3dtilesError): 47 | """ 48 | This exception will be thrown if the point cloud format isn't respected. 49 | """ 50 | 51 | 52 | class InvalidB3dmError(Invalid3dtilesError): 53 | """ 54 | This exception will be thrown if the batched 3D model format isn't respected. 55 | """ 56 | 57 | 58 | class InvalidTilesetError(Invalid3dtilesError): 59 | """ 60 | This exception will be thrown if the tileset format isn't respected. 61 | """ 62 | 63 | 64 | class BoundingVolumeMissingException(InvalidTilesetError): 65 | """ 66 | This exception will be thrown when a bounding volume is needed but not present. 67 | """ 68 | -------------------------------------------------------------------------------- /util/transverse_mercator.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of Blosm addon for Blender. 3 | Copyright (C) 2014-2018 Vladimir Elistratov 4 | prokitektura+support@gmail.com 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | import math 21 | 22 | # see conversion formulas at 23 | # http://en.wikipedia.org/wiki/Transverse_Mercator_projection 24 | # and 25 | # http://mathworld.wolfram.com/MercatorProjection.html 26 | class TransverseMercator: 27 | radius = 6378137. 28 | 29 | def __init__(self, **kwargs): 30 | # setting default values 31 | self.lat = 0. # in degrees 32 | self.lon = 0. # in degrees 33 | self.k = 1. # scale factor 34 | 35 | for attr in kwargs: 36 | setattr(self, attr, kwargs[attr]) 37 | self.latInRadians = math.radians(self.lat) 38 | 39 | def fromGeographic(self, lat, lon): 40 | lat = math.radians(lat) 41 | lon = math.radians(lon-self.lon) 42 | B = math.sin(lon) * math.cos(lat) 43 | x = 0.5 * self.k * self.radius * math.log((1.+B)/(1.-B)) 44 | y = self.k * self.radius * ( math.atan(math.tan(lat)/math.cos(lon)) - self.latInRadians ) 45 | return (x, y, 0.) 46 | 47 | def toGeographic(self, x, y): 48 | x = x/(self.k * self.radius) 49 | y = y/(self.k * self.radius) 50 | D = y + self.latInRadians 51 | lon = math.atan(math.sinh(x)/math.cos(D)) 52 | lat = math.asin(math.sin(D)/math.cosh(x)) 53 | 54 | lon = self.lon + math.degrees(lon) 55 | lat = math.degrees(lat) 56 | return (lat, lon) -------------------------------------------------------------------------------- /pml/antlr4/tree/RuleTagToken.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2012-2017 The ANTLR Project. All rights reserved. 3 | # Use of this file is governed by the BSD 3-clause license that 4 | # can be found in the LICENSE.txt file in the project root. 5 | # 6 | 7 | # 8 | # A {@link Token} object representing an entire subtree matched by a parser 9 | # rule; e.g., {@code }. These tokens are created for {@link TagChunk} 10 | # chunks where the tag corresponds to a parser rule. 11 | # 12 | from antlr4.Token import Token 13 | 14 | 15 | class RuleTagToken(Token): 16 | # 17 | # Constructs a new instance of {@link RuleTagToken} with the specified rule 18 | # name, bypass token type, and label. 19 | # 20 | # @param ruleName The name of the parser rule this rule tag matches. 21 | # @param bypassTokenType The bypass token type assigned to the parser rule. 22 | # @param label The label associated with the rule tag, or {@code null} if 23 | # the rule tag is unlabeled. 24 | # 25 | # @exception IllegalArgumentException if {@code ruleName} is {@code null} 26 | # or empty. 27 | 28 | def __init__(self, ruleName:str, bypassTokenType:int, label:str=None): 29 | if ruleName is None or len(ruleName)==0: 30 | raise Exception("ruleName cannot be null or empty.") 31 | self.source = None 32 | self.type = bypassTokenType # token type of the token 33 | self.channel = Token.DEFAULT_CHANNEL # The parser ignores everything not on DEFAULT_CHANNEL 34 | self.start = -1 # optional; return -1 if not implemented. 35 | self.stop = -1 # optional; return -1 if not implemented. 36 | self.tokenIndex = -1 # from 0..n-1 of the token object in the input stream 37 | self.line = 0 # line=1..n of the 1st character 38 | self.column = -1 # beginning of the line at which it occurs, 0..n-1 39 | self.label = label 40 | self._text = self.getText() # text of the token. 41 | 42 | self.ruleName = ruleName 43 | 44 | 45 | def getText(self): 46 | if self.label is None: 47 | return "<" + self.ruleName + ">" 48 | else: 49 | return "<" + self.label + ":" + self.ruleName + ">" 50 | -------------------------------------------------------------------------------- /geojson/__init__.py: -------------------------------------------------------------------------------- 1 | from manager import Manager as _Manager 2 | from building.manager import BuildingManager as _BuildingManager 3 | 4 | 5 | class Manager(_Manager): 6 | 7 | def parsePolygon(self, feature, featureId): 8 | # render it in 9 | feature.r = True 10 | 11 | def parseMultipolygon(self, feature, featureId): 12 | # render it in 13 | feature.r = True 14 | 15 | def render(self): 16 | data = self.data 17 | 18 | for polygon in data.polygons: 19 | if polygon.valid and polygon.r: 20 | renderer = polygon.rr or self.renderer 21 | renderer.preRender(polygon) 22 | renderer.renderPolygon(polygon, data) 23 | renderer.postRender(polygon) 24 | 25 | for multipolygon in data.multipolygons: 26 | if multipolygon.valid and multipolygon.r: 27 | renderer = multipolygon.rr or self.renderer 28 | renderer.preRender(multipolygon) 29 | renderer.renderMultiPolygon(multipolygon, data) 30 | renderer.postRender(multipolygon) 31 | 32 | for node in data.nodes: 33 | renderer = node.rr or self.nodeRenderer 34 | #renderer.preRender(node) 35 | renderer.renderNode(node, data) 36 | #renderer.postRender(node) 37 | 38 | 39 | class Building: 40 | """ 41 | A wrapper for a GeoJson building 42 | """ 43 | def __init__(self, element): 44 | self.outline = element 45 | self.parts = [] 46 | 47 | def addPart(self, part): 48 | self.parts.append(part) 49 | 50 | 51 | class BuildingManager(_BuildingManager): 52 | 53 | def process(self): 54 | pass 55 | 56 | def parsePolygon(self, feature, featureId): 57 | # create a wrapper for the GeoJson 58 | building = Building(feature) 59 | # store the related wrapper in the attribute 60 | feature.b = building 61 | self.buildings.append(building) 62 | 63 | def parseMultipolygon(self, feature, featureId): 64 | self.parsePolygon(feature, featureId) -------------------------------------------------------------------------------- /item/roof_item.py: -------------------------------------------------------------------------------- 1 | from . import Item 2 | from .roof_side import RoofSide 3 | 4 | _className = "Roof" 5 | 6 | 7 | class RoofItem(Item): 8 | 9 | def __init__(self): 10 | super().__init__() 11 | self.buildingPart = "roof" 12 | 13 | def setStyleBlock(self): 14 | footprint = self.footprint 15 | # Find style blocks in (actually in ), 16 | # also try to find them at the very top of the style definitions 17 | styleBlocks = footprint.styleBlock.styleBlocks.get( 18 | _className, 19 | footprint.buildingStyle.styleBlocks.get(_className) 20 | ) 21 | if styleBlocks: 22 | for styleBlock in styleBlocks: 23 | if self.evaluateCondition(styleBlock): 24 | self.styleBlock = styleBlock 25 | if styleBlock.markup: 26 | self.prepareMarkupItems() 27 | break 28 | 29 | def prepareMarkupItems(self): 30 | """ 31 | Get items for the markup style blocks 32 | """ 33 | self.markup.extend( 34 | _styleBlock.getItem(self.itemFactory, self)\ 35 | for _styleBlock in self.styleBlock.markup if self.evaluateCondition(_styleBlock) 36 | ) 37 | 38 | def getCladdingMaterial(self): 39 | return self.getStyleBlockAttr("roofCladdingMaterial") 40 | 41 | def getCladdingColor(self): 42 | return self.getStyleBlockAttr("roofCladdingColor") 43 | 44 | 45 | class RoofWithSidesItem(RoofItem): 46 | 47 | def __init__(self): 48 | super().__init__() 49 | # a Python list of instances of item.roof_side.RoofSide 50 | self.roofSides = [] 51 | 52 | def init(self): 53 | super().init() 54 | self.roofSides.clear() 55 | 56 | def addRoofSide(self, roofSideIndices, uvs, itemIndex, itemFactory): 57 | """ 58 | Args: 59 | itemIndex (int): for , for 60 | """ 61 | self.roofSides.append( 62 | RoofSide.getItem(itemFactory, self, roofSideIndices, uvs, itemIndex) 63 | ) -------------------------------------------------------------------------------- /threed_tiles/py3dtiles/tileset/content/tile_content.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from abc import ABC, abstractmethod 4 | from pathlib import Path 5 | from typing import Any, Literal 6 | 7 | import numpy as np 8 | import numpy.typing as npt 9 | 10 | from .batch_table import BatchTable 11 | from .feature_table import FeatureTable 12 | 13 | 14 | class TileContent(ABC): 15 | header: TileContentHeader 16 | body: TileContentBody 17 | 18 | def to_array(self) -> npt.NDArray[np.uint8]: 19 | self.sync() 20 | header_arr = self.header.to_array() 21 | body_arr = self.body.to_array() 22 | return np.concatenate((header_arr, body_arr)) 23 | 24 | def to_hex_str(self) -> str: 25 | arr = self.to_array() 26 | return " ".join(f"{x:02X}" for x in arr) 27 | 28 | def save_as(self, path: Path) -> None: 29 | tile_arr = self.to_array() 30 | with path.open("bw") as f: 31 | f.write(bytes(tile_arr)) 32 | 33 | @abstractmethod 34 | def print_info(self) -> None: 35 | ... 36 | 37 | @abstractmethod 38 | def sync(self) -> None: 39 | """ 40 | Allow to synchronize headers with contents. 41 | """ 42 | 43 | @staticmethod 44 | @abstractmethod 45 | def from_array(array: npt.NDArray[np.uint8]) -> TileContent: 46 | ... 47 | 48 | 49 | class TileContentHeader(ABC): 50 | magic_value: Literal[b"b3dm", b"pnts"] 51 | version: int 52 | 53 | def __init__(self) -> None: 54 | self.tile_byte_length = 0 55 | self.ft_json_byte_length = 0 56 | self.ft_bin_byte_length = 0 57 | self.bt_json_byte_length = 0 58 | self.bt_bin_byte_length = 0 59 | self.bt_length = 0 # number of models in the batch 60 | 61 | @staticmethod 62 | @abstractmethod 63 | def from_array(array: npt.NDArray[np.uint8]) -> TileContentHeader: 64 | ... 65 | 66 | @abstractmethod 67 | def to_array(self) -> npt.NDArray[np.uint8]: 68 | ... 69 | 70 | 71 | class TileContentBody(ABC): 72 | batch_table: BatchTable 73 | feature_table: FeatureTable[Any, Any] 74 | 75 | @abstractmethod 76 | def to_array(self) -> npt.NDArray[np.uint8]: 77 | ... 78 | -------------------------------------------------------------------------------- /item_renderer/texture/base/__init__.py: -------------------------------------------------------------------------------- 1 | from .item_renderer import ItemRendererMixin 2 | from .container import Container 3 | from ..facade import Facade as FacadeBase 4 | from ..div import Div as DivBase 5 | from ..level import Level as LevelBase 6 | from ..bottom import Bottom as BottomBase 7 | from .door import Door 8 | from .level import CurtainWall 9 | 10 | from ..roof_flat import RoofFlat as RoofFlatBase 11 | from .roof_flat_multi import RoofFlatMulti 12 | from ..roof_generatrix import RoofGeneratrix as RoofGeneratrixBase 13 | from ..roof_pyramidal import RoofPyramidal as RoofPyramidalBase 14 | from ..roof_profile import RoofProfile as RoofProfileBase 15 | from ..roof_hipped import RoofHipped as RoofHippedBase 16 | 17 | 18 | class Facade(FacadeBase, Container): 19 | 20 | def __init__(self): 21 | # a reference to the Container class used in the parent classes 22 | self.Container = Container 23 | Container.__init__(self, exportMaterials=False) 24 | 25 | 26 | class Div(DivBase, Container): 27 | 28 | def __init__(self): 29 | # a reference to the Container class used in the parent classes 30 | self.Container = Container 31 | Container.__init__(self, exportMaterials=False) 32 | 33 | 34 | class Level(LevelBase, Container): 35 | 36 | def __init__(self): 37 | # a reference to the Container class used in the parent classes 38 | self.Container = Container 39 | Container.__init__(self, exportMaterials=False) 40 | LevelBase.__init__(self) 41 | 42 | 43 | class Bottom(BottomBase, Container): 44 | 45 | def __init__(self): 46 | # a reference to the Container class used in the parent classes 47 | self.Container = Container 48 | Container.__init__(self, exportMaterials=False) 49 | BottomBase.__init__(self) 50 | 51 | 52 | class RoofFlat(RoofFlatBase, ItemRendererMixin): 53 | pass 54 | 55 | 56 | class RoofGeneratrix(RoofGeneratrixBase, ItemRendererMixin): 57 | pass 58 | 59 | 60 | class RoofPyramidal(RoofPyramidalBase, ItemRendererMixin): 61 | pass 62 | 63 | 64 | class RoofProfile(RoofProfileBase, ItemRendererMixin): 65 | pass 66 | 67 | 68 | class RoofHipped(RoofHippedBase, ItemRendererMixin): 69 | pass -------------------------------------------------------------------------------- /manager/logging.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of Blosm addon for Blender. 3 | Copyright (C) 2014-2018 Vladimir Elistratov 4 | prokitektura+support@gmail.com 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | from datetime import datetime 21 | from ..building.manager import BuildingManager 22 | 23 | 24 | class Logger: 25 | 26 | def __init__(self, app, osm): 27 | self.parseStartTime = datetime.now() 28 | app.logger = self 29 | self.app = app 30 | self.osm = osm 31 | print("Parsing OSM file %s..." % app.osmFilepath) 32 | 33 | def processStart(self): 34 | print("Time for parsing OSM file: %s" % (datetime.now() - self.parseStartTime)) 35 | self.processStartTime = datetime.now() 36 | print("Processing the parsed OSM data...") 37 | 38 | def processEnd(self): 39 | self.numBuildings() 40 | print("Time for processing of the parsed OSM data: %s" % (datetime.now() - self.processStartTime)) 41 | 42 | def renderStart(self): 43 | self.renderStartTime = datetime.now() 44 | print("Creating meshes in Blender...") 45 | 46 | def renderEnd(self): 47 | t = datetime.now() 48 | print("Time for mesh creation in Blender: %s" % (t - self.renderStartTime)) 49 | print("Total duration: %s" % (t - self.parseStartTime)) 50 | 51 | def numBuildings(self): 52 | app = self.app 53 | if app.mode is app.twoD or not app.buildings: 54 | return 55 | for m in app.managers: 56 | if isinstance(m, BuildingManager): 57 | print("The number of buildings: %s" % len(m.buildings)) -------------------------------------------------------------------------------- /item_renderer/texture/base/level.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from ....util.blender_extra.material import createMaterialFromTemplate, setImage 3 | 4 | from .container import Container 5 | from ..level import CurtainWall as CurtainWallBase 6 | from ...util import setTextureSize, setTextureSize2, getPath 7 | 8 | 9 | class CurtainWall(CurtainWallBase, Container): 10 | 11 | def __init__(self): 12 | # a reference to the Container class used in the parent classes 13 | self.Container = Container 14 | Container.__init__(self, exportMaterials=False) 15 | CurtainWallBase.__init__(self) 16 | 17 | def createFacadeMaterial(self, item, materialName, facadeTextureInfo, claddingTextureInfo, uvs): 18 | if not materialName in bpy.data.materials: 19 | materialTemplate = self.getFacadeMaterialTemplate( 20 | facadeTextureInfo, 21 | None 22 | ) 23 | nodes = createMaterialFromTemplate(materialTemplate, materialName) 24 | # the overlay texture 25 | image = setImage( 26 | facadeTextureInfo["name"], 27 | getPath(self.r, facadeTextureInfo["path"]), 28 | nodes, 29 | "Main" 30 | ) 31 | setTextureSize(facadeTextureInfo, image) 32 | # specular map 33 | if facadeTextureInfo.get("specularMapName"): 34 | setImage( 35 | facadeTextureInfo["specularMapName"], 36 | getPath(self.r, facadeTextureInfo["path"]), 37 | nodes, 38 | "Specular Map" 39 | ) 40 | 41 | setTextureSize2(facadeTextureInfo, materialName, "Main") 42 | return True 43 | 44 | def getFacadeMaterialTemplate(self, facadeTextureInfo, claddingTextureInfo): 45 | useCladdingColor = self.r.useCladdingColor 46 | useSpecularMap = facadeTextureInfo.get("specularMapName") 47 | 48 | if useSpecularMap and useCladdingColor: 49 | materialTemplateName = "facade_specular_color" 50 | elif useSpecularMap: 51 | materialTemplateName = "facade_specular" 52 | else: 53 | materialTemplateName = "export" 54 | 55 | return self.getMaterialTemplate(materialTemplateName) -------------------------------------------------------------------------------- /pml/antlr4/CommonTokenFactory.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2012-2017 The ANTLR Project. All rights reserved. 3 | # Use of this file is governed by the BSD 3-clause license that 4 | # can be found in the LICENSE.txt file in the project root. 5 | # 6 | 7 | # 8 | # This default implementation of {@link TokenFactory} creates 9 | # {@link CommonToken} objects. 10 | # 11 | from antlr4.Token import CommonToken 12 | 13 | class TokenFactory(object): 14 | 15 | pass 16 | 17 | class CommonTokenFactory(TokenFactory): 18 | # 19 | # The default {@link CommonTokenFactory} instance. 20 | # 21 | #

22 | # This token factory does not explicitly copy token text when constructing 23 | # tokens.

24 | # 25 | DEFAULT = None 26 | 27 | def __init__(self, copyText:bool=False): 28 | # Indicates whether {@link CommonToken#setText} should be called after 29 | # constructing tokens to explicitly set the text. This is useful for cases 30 | # where the input stream might not be able to provide arbitrary substrings 31 | # of text from the input after the lexer creates a token (e.g. the 32 | # implementation of {@link CharStream#getText} in 33 | # {@link UnbufferedCharStream} throws an 34 | # {@link UnsupportedOperationException}). Explicitly setting the token text 35 | # allows {@link Token#getText} to be called at any time regardless of the 36 | # input stream implementation. 37 | # 38 | #

39 | # The default value is {@code false} to avoid the performance and memory 40 | # overhead of copying text for every token unless explicitly requested.

41 | # 42 | self.copyText = copyText 43 | 44 | def create(self, source, type:int, text:str, channel:int, start:int, stop:int, line:int, column:int): 45 | t = CommonToken(source, type, channel, start, stop) 46 | t.line = line 47 | t.column = column 48 | if text is not None: 49 | t.text = text 50 | elif self.copyText and source[1] is not None: 51 | t.text = source[1].getText(start,stop) 52 | return t 53 | 54 | def createThin(self, type:int, text:str): 55 | t = CommonToken(type=type) 56 | t.text = text 57 | return t 58 | 59 | CommonTokenFactory.DEFAULT = CommonTokenFactory() -------------------------------------------------------------------------------- /way/manager.py: -------------------------------------------------------------------------------- 1 | from . import RealWay 2 | 3 | 4 | _allWays = ( 5 | "motorway", 6 | "trunk", 7 | "primary", 8 | "secondary", 9 | "tertiary", 10 | "unclassified", 11 | "residential", 12 | "service", 13 | "pedestrian", 14 | "track", 15 | "footway", 16 | "steps", 17 | "cycleway", 18 | "bridleway", 19 | "other" 20 | ) 21 | 22 | 23 | _facadeVisibilityWays = ( 24 | "motorway", 25 | "trunk", 26 | "primary", 27 | "secondary", 28 | "tertiary", 29 | "unclassified", 30 | "residential", 31 | #"service", 32 | "pedestrian", 33 | "track", 34 | #"footway", 35 | #"steps", 36 | #"cycleway", 37 | #"bridleway", 38 | #"other" 39 | ) 40 | 41 | 42 | class RealWayManager: 43 | 44 | def __init__(self, data, app): 45 | self.id = "ways" 46 | self.data = data 47 | self.app = app 48 | 49 | # use the default layer class in the 50 | self.layerClass = None 51 | 52 | # don't accept broken multipolygons 53 | self.acceptBroken = False 54 | 55 | self.layers = dict((layerId, []) for layerId in _allWays) 56 | 57 | self.actions = [] 58 | 59 | app.addManager(self) 60 | 61 | def parseWay(self, element, elementId): 62 | self.createRealWay(element) 63 | 64 | def parseRelation(self, element, elementId): 65 | return 66 | 67 | def createRealWay(self, element): 68 | # create a wrapper for the OSM way 69 | self.layers[element.l.mlId].append( RealWay(element) ) 70 | 71 | def getAllWays(self): 72 | return (way for layerId in _allWays for way in self.layers[layerId]) 73 | 74 | def getFacadeVisibilityWays(self): 75 | return (way for layerId in _facadeVisibilityWays for way in self.layers[layerId]) 76 | 77 | def process(self): 78 | for action in self.actions: 79 | action.do(self) 80 | 81 | def setRenderer(self, renderer, app): 82 | self.renderer = renderer 83 | app.addRenderer(renderer) 84 | 85 | def render(self): 86 | for way in self.getAllWays(): 87 | self.renderer.render(way, self.data) 88 | 89 | def addAction(self, action): 90 | action.app = self.app 91 | self.actions.append(action) -------------------------------------------------------------------------------- /pml/antlr4/atn/ATNSimulator.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2012-2017 The ANTLR Project. All rights reserved. 3 | # Use of this file is governed by the BSD 3-clause license that 4 | # can be found in the LICENSE.txt file in the project root. 5 | #/ 6 | from antlr4.PredictionContext import PredictionContextCache, PredictionContext, getCachedPredictionContext 7 | from antlr4.atn.ATN import ATN 8 | from antlr4.atn.ATNConfigSet import ATNConfigSet 9 | from antlr4.dfa.DFAState import DFAState 10 | 11 | 12 | class ATNSimulator(object): 13 | 14 | # Must distinguish between missing edge and edge we know leads nowhere#/ 15 | ERROR = DFAState(configs=ATNConfigSet()) 16 | ERROR.stateNumber = 0x7FFFFFFF 17 | 18 | # The context cache maps all PredictionContext objects that are == 19 | # to a single cached copy. This cache is shared across all contexts 20 | # in all ATNConfigs in all DFA states. We rebuild each ATNConfigSet 21 | # to use only cached nodes/graphs in addDFAState(). We don't want to 22 | # fill this during closure() since there are lots of contexts that 23 | # pop up but are not used ever again. It also greatly slows down closure(). 24 | # 25 | #

This cache makes a huge difference in memory and a little bit in speed. 26 | # For the Java grammar on java.*, it dropped the memory requirements 27 | # at the end from 25M to 16M. We don't store any of the full context 28 | # graphs in the DFA because they are limited to local context only, 29 | # but apparently there's a lot of repetition there as well. We optimize 30 | # the config contexts before storing the config set in the DFA states 31 | # by literally rebuilding them with cached subgraphs only.

32 | # 33 | #

I tried a cache for use during closure operations, that was 34 | # whacked after each adaptivePredict(). It cost a little bit 35 | # more time I think and doesn't save on the overall footprint 36 | # so it's not worth the complexity.

37 | #/ 38 | def __init__(self, atn:ATN, sharedContextCache:PredictionContextCache): 39 | self.atn = atn 40 | self.sharedContextCache = sharedContextCache 41 | 42 | def getCachedContext(self, context:PredictionContext): 43 | if self.sharedContextCache is None: 44 | return context 45 | visited = dict() 46 | return getCachedPredictionContext(context, self.sharedContextCache, visited) 47 | 48 | -------------------------------------------------------------------------------- /parse/osm/node.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of Blosm addon for Blender. 3 | Copyright (C) 2014-2018 Vladimir Elistratov 4 | prokitektura+support@gmail.com 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | class Node: 21 | """ 22 | A class to represent an OSM node 23 | 24 | Some attributes: 25 | l (app.Layer): layer used to place the related geometry to a specific Blender object 26 | tags (dict): OSM tags 27 | m: A manager used during the rendering; if None, applies defaults 28 | during the rendering 29 | rr: A special renderer for the OSM node 30 | b (dict): Here we store building indices (i.e. the indices of instances of 31 | the wrapper class in Python list of an instance 32 | of ) 33 | w (dict): Here we store ids of OSM that represent real ways. It is used only by the 34 | RealWayManager responsible for real ways 35 | rr: A renderer for the OSM node 36 | """ 37 | __slots__ = ("l", "tags", "lat", "lon", "coords", "b", "w", "rr", "valid", "m") 38 | 39 | def __init__(self, lat, lon, tags): 40 | self.tags = tags 41 | self.lat = lat 42 | self.lon = lon 43 | self.b = dict() 44 | self.w = dict() 45 | # projected coordinates 46 | self.coords = None 47 | self.rr = None 48 | self.valid = True 49 | 50 | def getData(self, osm): 51 | """ 52 | Get projected coordinates 53 | """ 54 | if not self.coords: 55 | # preserve coordinates in the local system of reference for a future use 56 | self.coords = osm.projection.fromGeographic(self.lat, self.lon) 57 | return self.coords -------------------------------------------------------------------------------- /action/terrain.py: -------------------------------------------------------------------------------- 1 | from . import Action 2 | import parse 3 | 4 | from util import zAxis 5 | 6 | 7 | class Terrain(Action): 8 | 9 | def preprocess(self, buildingsP): 10 | # means "buildings from the parser" 11 | self.app.terrain.initProjectionProxy(buildingsP, self.data) 12 | 13 | def do(self, building, itemClass, style, globalRenderer): 14 | #self.projectSingleVertex(building) 15 | self.projectAllVertices(building) 16 | 17 | def projectAllVertices(self, building): 18 | outline = building.outline 19 | 20 | maxZ = max( 21 | ( 22 | self.app.terrain.project2(vert)\ 23 | for vert in\ 24 | (outline.getOuterData(self.data) if outline.t is parse.multipolygon else outline.getData(self.data)) 25 | ), 26 | key = lambda vert: vert[2] 27 | )[2] 28 | 29 | if maxZ == self.app.terrain.projectLocation: 30 | # the building is outside the terrain so skip the whole building 31 | self.skipBuilding() 32 | return 33 | 34 | # we use the lowest z-coordinate among the footprint vertices projected on the terrain as 35 | offsetZ = min( 36 | ( 37 | self.app.terrain.project2(vert)\ 38 | for vert in\ 39 | (outline.getOuterData(self.data) if outline.t is parse.multipolygon else outline.getData(self.data)) 40 | ), 41 | key = lambda vert: vert[2] 42 | ) 43 | building.offset = offsetZ[2] * zAxis 44 | # we also need to store the altitude difference for the building footprint 45 | building.altitudeDifference = maxZ - offsetZ[2] 46 | 47 | def projectSingleVertex(self, building): 48 | outline = building.outline 49 | # take the first vertex of the outline as the offset 50 | offsetZ = self.app.terrain.project( 51 | next( outline.getOuterData(self.data) if outline.t is parse.multipolygon else outline.getData(self.data) ) 52 | ) 53 | if offsetZ: 54 | building.offset = offsetZ[2] * zAxis 55 | else: 56 | # the building is outside the terrain so skip the whole building 57 | self.skipBuilding() 58 | 59 | def skipBuilding(self): 60 | self.itemStore.skip = True 61 | 62 | def cleanup(self): 63 | self.app.terrain.cleanupProjectionProxy() -------------------------------------------------------------------------------- /realistic/building/roof/skillion.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of Blosm addon for Blender. 3 | Copyright (C) 2014-2018 Vladimir Elistratov 4 | prokitektura+support@gmail.com 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | from . import RoofRealistic 21 | from building.roof.skillion import RoofSkillion 22 | 23 | 24 | class RoofSkillionRealistic(RoofRealistic, RoofSkillion): 25 | 26 | def renderWalls(self): 27 | if self.mrw: 28 | bm = self.r.bm 29 | verts = self.verts 30 | uvLayer = bm.loops.layers.uv[0] 31 | uvLayerSize = bm.loops.layers.uv[1] 32 | # The variable is used if there are wall faces (exactly two!) 33 | # composed of 3 vertices 34 | firstFace = True 35 | # create BMesh faces for the building walls 36 | for f in (bm.faces.new(verts[i] for i in indices) for indices in self.wallIndices): 37 | origin = f.verts[0].co 38 | originZ = origin[2] 39 | w = (f.verts[1].co - origin).length 40 | size = None if self.noWalls else (w, self.wallHeight) 41 | f.loops[0][uvLayer].uv = (0., 0.) 42 | f.loops[1][uvLayer].uv = (w, 0.) 43 | if len(f.verts) == 4: 44 | f.loops[2][uvLayer].uv = (w, f.verts[2].co[2] - originZ) 45 | f.loops[3][uvLayer].uv = (0., f.verts[3].co[2] - originZ) 46 | else: # len(f.verts) == 3 47 | f.loops[2][uvLayer].uv = (w if firstFace else 0., f.verts[2].co[2] - originZ) 48 | firstFace = False 49 | if size: 50 | for l in f.loops: 51 | l[uvLayerSize].uv = size 52 | self.mrw.renderWalls(f, w) 53 | else: 54 | RoofSkillion.renderWalls(self) -------------------------------------------------------------------------------- /item_renderer/texture/roof_pyramidal.py: -------------------------------------------------------------------------------- 1 | import math 2 | from .. import ItemRenderer 3 | from ..util import initUvAlongPolygonEdge 4 | from grammar import smoothness 5 | 6 | from util import zAxis 7 | 8 | 9 | class RoofPyramidal(ItemRenderer): 10 | 11 | def render(self, roofItem): 12 | smoothFaces = roofItem.getStyleBlockAttr("faces") is smoothness.Smooth 13 | 14 | footprint = roofItem.footprint 15 | polygon = footprint.polygon 16 | building = roofItem.building 17 | verts = building.verts 18 | # the index of the first vertex of the polygon that defines the roof base 19 | firstVertIndex = roofItem.firstVertIndex 20 | n = polygon.n 21 | lastVertIndex = firstVertIndex+n-1 22 | 23 | roofHeight = footprint.roofHeight 24 | center = polygon.centerBB(footprint.roofVerticalPosition) 25 | 26 | # create a vertex at the center 27 | verts.append(center + roofHeight*zAxis) 28 | 29 | for pi, vi in zip(range(n-1), range(firstVertIndex, lastVertIndex)): 30 | # Create a petal of quads, i.e. the quads are created along the generatrix 31 | 32 | # is a unit vector along the base edge 33 | uVec, uv0, uv1 = initUvAlongPolygonEdge(polygon, pi, pi+1) 34 | 35 | # create a triangle 36 | self.createFace( 37 | roofItem, 38 | smoothFaces, 39 | (vi, vi+1, -1), 40 | uVec, uv0, uv1 41 | ) 42 | 43 | # create the closing triangle 44 | # is a unit vector along the base edge 45 | uVec, uv0, uv1 = initUvAlongPolygonEdge(polygon, -1, 0) 46 | self.createFace( 47 | roofItem, 48 | smoothFaces, 49 | (lastVertIndex, firstVertIndex, -1), 50 | uVec, uv0, uv1 51 | ) 52 | 53 | def createFace(self, roofItem, smooth, indices, uVec, uv0, uv1): 54 | face = self.r.createFace(roofItem.building, indices) 55 | if smooth: 56 | face.smooth = smooth 57 | 58 | # assign UV-coordinates 59 | verts = roofItem.building.verts 60 | vec2 = verts[indices[2]]-verts[indices[0]] 61 | vec2u = vec2.dot(uVec) 62 | 63 | self.renderCladding( 64 | roofItem, 65 | face, 66 | (uv0, uv1, (vec2u+uv0[0], (vec2 - vec2u*uVec).length+uv0[1])) 67 | ) -------------------------------------------------------------------------------- /realistic/material/colors.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of Blosm addon for Blender. 3 | Copyright (C) 2014-2018 Vladimir Elistratov 4 | prokitektura+support@gmail.com 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | glassColors = ( 21 | (0.306, 0.447, 0.573), 22 | (0.169, 0.318, 0.361), 23 | (0.094, 0.18, 0.271), 24 | (0.082, 0.396, 0.4), 25 | (0.208, 0.478, 0.608), 26 | (0.027, 0.133, 0.208), 27 | (0.224, 0.424, 0.365), 28 | (0.282, 0.506, 0.584), 29 | (0.141, 0.204, 0.318), 30 | (0.059, 0.165, 0.2) 31 | ) 32 | 33 | 34 | brickColors = ( 35 | (0.463, 0.294, 0.227), 36 | (0.788, 0.788, 0.788), 37 | (0.69, 0.588, 0.286), 38 | (0.357, 0.271, 0.239), 39 | (0.725, 0.725, 0.725), 40 | (0.545, 0.482, 0.267), 41 | (0.984, 0.655, 0.447), 42 | (0.773, 0.671, 0.369), 43 | (0.608, 0.208, 0.078), 44 | (0.863, 0.863, 0.863) 45 | ) 46 | 47 | 48 | plasterColors = ( 49 | (0.741, 0.757, 0.784), 50 | (0.847, 0.722, 0.388), 51 | (0.584, 0.675, 0.596), 52 | (0.82, 0.745, 0.62), 53 | (0.859, 0.533, 0.4), 54 | (0.259, 0.51, 0.329), 55 | (0.831, 0.745, 0.553), 56 | (0.824, 0.741, 0.384), 57 | (0.62, 0.502, 0.541), 58 | (0.757, 0.698, 0.584), 59 | ) 60 | 61 | 62 | concreteColors = ( 63 | (0.686, 0.686, 0.686), 64 | (0.698, 0.698, 0.651), 65 | (0.784, 0.761, 0.714), 66 | (0.545, 0.545, 0.553), 67 | (0.655, 0.651, 0.631) 68 | ) 69 | 70 | 71 | roofTilesColors = ( 72 | (0.604, 0.333, 0.243), 73 | (0.463, 0.435, 0.463), 74 | (0.667, 0.51, 0.478), 75 | (0.682, 0.643, 0.596), 76 | (0.816, 0.541, 0.439), 77 | (0.357, 0.322, 0.255), 78 | (0.357, 0.376, 0.416) 79 | ) 80 | 81 | 82 | metalColors = ( 83 | (0.686, 0.686, 0.686), 84 | (0.698, 0.698, 0.651), 85 | (0.784, 0.761, 0.714), 86 | (0.545, 0.545, 0.553), 87 | (0.655, 0.651, 0.631) 88 | ) -------------------------------------------------------------------------------- /item_renderer/texture/roof_flat.py: -------------------------------------------------------------------------------- 1 | from .. import ItemRenderer 2 | from util import zAxis 3 | 4 | 5 | class RoofFlat(ItemRenderer): 6 | 7 | def render(self, roofItem): 8 | building = roofItem.building 9 | face = self.r.createFace( 10 | building, 11 | range(roofItem.firstVertIndex, roofItem.firstVertIndex+roofItem.footprint.polygon.n) 12 | ) 13 | 14 | cl = roofItem.getStyleBlockAttr("cl") 15 | if cl: 16 | self.renderClass(roofItem, cl, face, None) 17 | else: 18 | self.renderCladding(roofItem, face, None) 19 | 20 | def setCladdingUvs(self, roofItem, face, claddingTextureInfo, uvs): 21 | textureWidthM = claddingTextureInfo["textureWidthM"] 22 | textureHeightM = textureWidthM * claddingTextureInfo["textureSize"][1] / claddingTextureInfo["textureSize"][0] 23 | 24 | polygon = roofItem.footprint.polygon 25 | verts = polygon.allVerts 26 | indices = polygon.indices 27 | 28 | # Arrange the texture along the longest edge of , 29 | # so the longest edges surves as u-axis for the texture 30 | maxEdgeIndex = polygon.maxEdgeIndex 31 | offset = verts[indices[maxEdgeIndex]] 32 | uVec = (verts[indices[maxEdgeIndex+1]] - offset) 33 | uVec.normalize() 34 | vVec = zAxis.cross(uVec) 35 | 36 | self.r.setUvs( 37 | face, 38 | # a generator! 39 | ( 40 | ( 41 | (verts[indices[i]]-offset).dot(uVec)/textureWidthM, 42 | (verts[indices[i]]-offset).dot(vVec)/textureHeightM 43 | ) for i in range(polygon.n) 44 | ), 45 | self.r.layer.uvLayerNameCladding 46 | ) 47 | 48 | def setClassUvs(self, item, face, uvs, texUl, texVb, texUr, texVt): 49 | polygon = item.footprint.polygon 50 | verts = polygon.allVerts 51 | indices = polygon.indices 52 | 53 | # Arrange the texture along the longest edge of , 54 | # so the longest edges surves as u-axis for the texture 55 | maxEdgeIndex = polygon.maxEdgeIndex 56 | uVec = ( verts[indices[maxEdgeIndex+1]] - verts[indices[maxEdgeIndex]] ) 57 | uVec.normalize() 58 | vVec = zAxis.cross(uVec) 59 | 60 | uvs = tuple( 61 | ( verts[indices[i]].dot(uVec), verts[indices[i]].dot(vVec) )\ 62 | for i in range(polygon.n) 63 | ) 64 | 65 | self._setRoofClassUvs(face, uvs, texUl, texVb, texUr, texVt) -------------------------------------------------------------------------------- /script/color_ramp_emission.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of Blosm addon for Blender. 3 | Copyright (C) 2014-2018 Vladimir Elistratov 4 | prokitektura+support@gmail.com 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | """ 21 | A script to assign colors to the Color Ramp node responsible for colors and strengths of 22 | building window emission. 23 | """ 24 | import bpy 25 | import random 26 | 27 | nodeGroupName = "WindowEmission" 28 | 29 | # the number of elements in the color ramp (32 is the limit imposed by Blender) 30 | numElements = 32 31 | 32 | colors = ( 33 | (0.847, 0.418, 0.087), 34 | (1., 0.931, 0.455), 35 | (0.847, 0.671, 0.325), 36 | (1., 0.527, 0.297) 37 | ) 38 | # emission strengths 39 | strengths = (0.5, 0.4, 0.42, 0.32) 40 | 41 | numColors = len(colors) 42 | numStrengths = len(strengths) 43 | 44 | colorRampStep = 1./numElements 45 | 46 | nodes = bpy.data.node_groups[nodeGroupName].nodes 47 | nodes["Modulo"].inputs[1].default_value = numElements 48 | nodes["Divide"].inputs[1].default_value = numElements 49 | nodes["Add"].inputs[1].default_value = colorRampStep/2. 50 | 51 | # elements of the color ramp 52 | elements = nodes["ColorRamp"].color_ramp.elements 53 | 54 | # remove all but the very first one elements 55 | for i in range(len(elements)-1, 0, -1): 56 | elements.remove(elements[i]) 57 | 58 | elements[0].position = 0. 59 | 60 | # create elements 61 | for i in range(1, numElements): 62 | elements.new(i*colorRampStep) 63 | 64 | # Set color with alpha for each element of the color ramp. 65 | # The alpha serves as emission strength; it will be multiplied by 10 in the Cycles material 66 | for i in range(numElements): 67 | c = elements[i].color 68 | # a random integer between 0 and numColors 69 | colorIndex = random.randrange(0, numColors) 70 | # a random integer between 0 and numStrengths 71 | strengthIndex = random.randrange(0, numStrengths) 72 | c[0] = colors[colorIndex][0] 73 | c[1] = colors[colorIndex][1] 74 | c[2] = colors[colorIndex][2] 75 | c[3] = strengths[strengthIndex] 76 | -------------------------------------------------------------------------------- /util/random.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of Blosm addon for Blender. 3 | Copyright (C) 2014-2018 Vladimir Elistratov 4 | prokitektura+support@gmail.com 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | from random import normalvariate, randrange 21 | from builtins import property 22 | 23 | 24 | class RandomNormal: 25 | 26 | def __init__(self, mean, sigmaRatio=0.05, numValues=100): 27 | self.numValues = numValues 28 | sigma = abs(sigmaRatio*mean) 29 | self.values = tuple(normalvariate(mean, sigma) for _ in range(numValues)) 30 | # the current index 31 | self.index = -1 32 | 33 | @property 34 | def value(self): 35 | self.index += 1 36 | if self.index == self.numValues: 37 | self.index = 0 38 | 39 | return self.values[self.index] 40 | 41 | 42 | class RandomWeighted: 43 | 44 | # the number of random indices for 45 | numIndices = 1000 46 | 47 | def __init__(self, distribution): 48 | """ 49 | Args: 50 | distribution (tuple): A tuple of tuples (value, its relative weight between integer 1 and 100) 51 | """ 52 | self.singleValue = None 53 | distrList = [] 54 | 55 | if len(distribution) == 1: 56 | self.singleValue = distribution[0][0] 57 | else: 58 | for n,w in distribution: 59 | distrList.extend(n for _ in range(w)) 60 | self.distrList = distrList 61 | lenDistrList = len(distrList) 62 | self.indices = tuple(randrange(lenDistrList) for _ in range(self.numIndices)) 63 | # the current index in which in turn points to 64 | self.index = -1 65 | 66 | @property 67 | def value(self): 68 | if self.singleValue is None: 69 | self.index += 1 70 | if self.index == self.numIndices: 71 | self.index = 0 72 | return self.distrList[ self.indices[self.index] ] 73 | else: 74 | return self.singleValue -------------------------------------------------------------------------------- /pml/antlr4/dfa/DFASerializer.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2012-2017 The ANTLR Project. All rights reserved. 3 | # Use of this file is governed by the BSD 3-clause license that 4 | # can be found in the LICENSE.txt file in the project root. 5 | #/ 6 | 7 | # A DFA walker that knows how to dump them to serialized strings.#/ 8 | from io import StringIO 9 | from antlr4 import DFA 10 | from antlr4.Utils import str_list 11 | from antlr4.dfa.DFAState import DFAState 12 | 13 | 14 | class DFASerializer(object): 15 | 16 | def __init__(self, dfa:DFA, literalNames:list=None, symbolicNames:list=None): 17 | self.dfa = dfa 18 | self.literalNames = literalNames 19 | self.symbolicNames = symbolicNames 20 | 21 | def __str__(self): 22 | if self.dfa.s0 is None: 23 | return None 24 | with StringIO() as buf: 25 | for s in self.dfa.sortedStates(): 26 | n = 0 27 | if s.edges is not None: 28 | n = len(s.edges) 29 | for i in range(0, n): 30 | t = s.edges[i] 31 | if t is not None and t.stateNumber != 0x7FFFFFFF: 32 | buf.write(self.getStateString(s)) 33 | label = self.getEdgeLabel(i) 34 | buf.write("-") 35 | buf.write(label) 36 | buf.write("->") 37 | buf.write(self.getStateString(t)) 38 | buf.write('\n') 39 | output = buf.getvalue() 40 | if len(output)==0: 41 | return None 42 | else: 43 | return output 44 | 45 | def getEdgeLabel(self, i:int): 46 | if i==0: 47 | return "EOF" 48 | if self.literalNames is not None and i<=len(self.literalNames): 49 | return self.literalNames[i-1] 50 | elif self.symbolicNames is not None and i<=len(self.symbolicNames): 51 | return self.symbolicNames[i-1] 52 | else: 53 | return str(i-1) 54 | 55 | def getStateString(self, s:DFAState): 56 | n = s.stateNumber 57 | baseStateStr = ( ":" if s.isAcceptState else "") + "s" + str(n) + ( "^" if s.requiresFullContext else "") 58 | if s.isAcceptState: 59 | if s.predicates is not None: 60 | return baseStateStr + "=>" + str_list(s.predicates) 61 | else: 62 | return baseStateStr + "=>" + str(s.prediction) 63 | else: 64 | return baseStateStr 65 | 66 | class LexerDFASerializer(DFASerializer): 67 | 68 | def __init__(self, dfa:DFA): 69 | super().__init__(dfa, None) 70 | 71 | def getEdgeLabel(self, i:int): 72 | return "'" + chr(i) + "'" 73 | -------------------------------------------------------------------------------- /_blender_manifest.toml: -------------------------------------------------------------------------------- 1 | schema_version = "1.0.0" 2 | 3 | id = "blosm" 4 | version = "2.7.9" 5 | name = "Blosm" 6 | tagline = "Import of Google 3D cities, OpenStreetMap, satellite imagery, 3D Tiles" 7 | maintainer = "Vladimir Elistratov " 8 | # Supported types: "add-on", "theme" 9 | type = "add-on" 10 | 11 | # Optional link to documentation, support, source files, etc 12 | website = "https://github.com/vvoovv/blosm/wiki/Premium-Version" 13 | 14 | # Optional list defined by Blender and server, see: 15 | # https://docs.blender.org/manual/en/dev/advanced/extensions/tags.html 16 | tags = ["Import-Export"] 17 | 18 | blender_version_min = "4.2.0" 19 | 20 | # License conforming to https://spdx.org/licenses/ (use "SPDX: prefix) 21 | # https://docs.blender.org/manual/en/dev/advanced/extensions/licenses.html 22 | license = [ 23 | "SPDX:GPL-2.0-or-later", 24 | ] 25 | # Optional: required by some licenses. 26 | # copyright = [ 27 | # "2002-2024 Developer Name", 28 | # "1998 Company Name", 29 | # ] 30 | 31 | # Optional list of supported platforms. If omitted, the extension will be available in all operating systems. 32 | # platforms = ["windows-x64", "macos-arm64", "linux-x64"] 33 | # Other supported platforms: "windows-arm64", "macos-x64" 34 | 35 | # Optional: bundle 3rd party Python modules. 36 | # https://docs.blender.org/manual/en/dev/advanced/extensions/python_wheels.html 37 | # wheels = [ 38 | # "./wheels/hexdump-3.3-py3-none-any.whl", 39 | # "./wheels/jsmin-3.0.1-py3-none-any.whl", 40 | # ] 41 | 42 | # # Optional: add-ons can list which resources they will require: 43 | # # * files (for access of any filesystem operations) 44 | # # * network (for internet access) 45 | # # * clipboard (to read and/or write the system clipboard) 46 | # # * camera (to capture photos and videos) 47 | # # * microphone (to capture audio) 48 | # # 49 | # # If using network, remember to also check `bpy.app.online_access` 50 | # # https://docs.blender.org/manual/en/dev/advanced/extensions/addons.html#internet-access 51 | # # 52 | # # For each permission it is important to also specify the reason why it is required. 53 | # # Keep this a single short sentence without a period (.) at the end. 54 | # # For longer explanations use the documentation or detail page. 55 | # 56 | # [permissions] 57 | network = "Download data for import (3D Tiles, OpenStreetMap, satellite imagery, terrain)" 58 | files = "Store and access the downloaded data on the filesystem" 59 | clipboard = "Copy and paste the coordinates of an area of interest" 60 | 61 | # Optional: build settings. 62 | # https://docs.blender.org/manual/en/dev/advanced/extensions/command_line_arguments.html#command-line-args-extension-build 63 | # [build] 64 | paths_exclude_pattern = [ 65 | "__pycache__/", 66 | "/.git/", 67 | "/*.zip", 68 | ] -------------------------------------------------------------------------------- /item_renderer/texture/export/__init__.py: -------------------------------------------------------------------------------- 1 | from .item_renderer import ItemRendererMixin 2 | from .container import Container 3 | from ..facade import Facade as FacadeBase 4 | from ..div import Div as DivBase 5 | from ..level import Level as LevelBase 6 | from ..bottom import Bottom as BottomBase 7 | from .door import Door 8 | from .level import CurtainWall 9 | 10 | from ..roof_flat import RoofFlat as RoofFlatBase 11 | from ..roof_flat_multi import RoofFlatMulti as RoofFlatMultiBase 12 | from ..roof_generatrix import RoofGeneratrix as RoofGeneratrixBase 13 | from ..roof_pyramidal import RoofPyramidal as RoofPyramidalBase 14 | from ..roof_profile import RoofProfile as RoofProfileBase 15 | from ..roof_hipped import RoofHipped as RoofHippedBase 16 | 17 | 18 | class Facade(FacadeBase, Container): 19 | 20 | def __init__(self): 21 | # a reference to the Container class used in the parent classes 22 | self.Container = Container 23 | Container.__init__(self, exportMaterials=True) 24 | 25 | 26 | class Div(DivBase, Container): 27 | 28 | def __init__(self): 29 | # a reference to the Container class used in the parent classes 30 | self.Container = Container 31 | Container.__init__(self, exportMaterials=True) 32 | 33 | 34 | class Level(LevelBase, Container): 35 | 36 | def __init__(self): 37 | # a reference to the Container class used in the parent classes 38 | self.Container = Container 39 | Container.__init__(self, exportMaterials=True) 40 | LevelBase.__init__(self) 41 | 42 | 43 | class Bottom(BottomBase, Container): 44 | 45 | def __init__(self): 46 | # a reference to the Container class used in the parent classes 47 | self.Container = Container 48 | Container.__init__(self, exportMaterials=True) 49 | BottomBase.__init__(self) 50 | 51 | 52 | class RoofFlat(RoofFlatBase, ItemRendererMixin): 53 | 54 | def __init__(self): 55 | super().__init__(exportMaterials=True) 56 | 57 | 58 | class RoofFlatMulti(RoofFlatMultiBase, ItemRendererMixin): 59 | 60 | def __init__(self): 61 | super().__init__(exportMaterials=True) 62 | 63 | 64 | class RoofGeneratrix(RoofGeneratrixBase, ItemRendererMixin): 65 | 66 | def __init__(self, generatrix, basePointPosition): 67 | super().__init__(generatrix, basePointPosition, exportMaterials=True) 68 | 69 | 70 | class RoofPyramidal(RoofPyramidalBase, ItemRendererMixin): 71 | 72 | def __init__(self): 73 | super().__init__(exportMaterials=True) 74 | 75 | 76 | class RoofProfile(RoofProfileBase, ItemRendererMixin): 77 | 78 | def __init__(self): 79 | super().__init__(exportMaterials=True) 80 | 81 | 82 | class RoofHipped(RoofHippedBase, ItemRendererMixin): 83 | 84 | def __init__(self): 85 | super().__init__(exportMaterials=True) -------------------------------------------------------------------------------- /pml/antlr4/error/ErrorListener.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2012-2017 The ANTLR Project. All rights reserved. 3 | # Use of this file is governed by the BSD 3-clause license that 4 | # can be found in the LICENSE.txt file in the project root. 5 | 6 | # Provides an empty default implementation of {@link ANTLRErrorListener}. The 7 | # default implementation of each method does nothing, but can be overridden as 8 | # necessary. 9 | 10 | 11 | import sys 12 | 13 | class ErrorListener(object): 14 | 15 | def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e): 16 | pass 17 | 18 | def reportAmbiguity(self, recognizer, dfa, startIndex, stopIndex, exact, ambigAlts, configs): 19 | pass 20 | 21 | def reportAttemptingFullContext(self, recognizer, dfa, startIndex, stopIndex, conflictingAlts, configs): 22 | pass 23 | 24 | def reportContextSensitivity(self, recognizer, dfa, startIndex, stopIndex, prediction, configs): 25 | pass 26 | 27 | class ConsoleErrorListener(ErrorListener): 28 | # 29 | # Provides a default instance of {@link ConsoleErrorListener}. 30 | # 31 | INSTANCE = None 32 | 33 | # 34 | # {@inheritDoc} 35 | # 36 | #

37 | # This implementation prints messages to {@link System#err} containing the 38 | # values of {@code line}, {@code charPositionInLine}, and {@code msg} using 39 | # the following format.

40 | # 41 | #
42 |     # line line:charPositionInLine msg
43 |     # 
44 | # 45 | def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e): 46 | print("line " + str(line) + ":" + str(column) + " " + msg, file=sys.stderr) 47 | 48 | ConsoleErrorListener.INSTANCE = ConsoleErrorListener() 49 | 50 | class ProxyErrorListener(ErrorListener): 51 | 52 | def __init__(self, delegates): 53 | super().__init__() 54 | if delegates is None: 55 | raise ReferenceError("delegates") 56 | self.delegates = delegates 57 | 58 | def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e): 59 | for delegate in self.delegates: 60 | delegate.syntaxError(recognizer, offendingSymbol, line, column, msg, e) 61 | 62 | def reportAmbiguity(self, recognizer, dfa, startIndex, stopIndex, exact, ambigAlts, configs): 63 | for delegate in self.delegates: 64 | delegate.reportAmbiguity(recognizer, dfa, startIndex, stopIndex, exact, ambigAlts, configs) 65 | 66 | def reportAttemptingFullContext(self, recognizer, dfa, startIndex, stopIndex, conflictingAlts, configs): 67 | for delegate in self.delegates: 68 | delegate.reportAttemptingFullContext(recognizer, dfa, startIndex, stopIndex, conflictingAlts, configs) 69 | 70 | def reportContextSensitivity(self, recognizer, dfa, startIndex, stopIndex, prediction, configs): 71 | for delegate in self.delegates: 72 | delegate.reportContextSensitivity(recognizer, dfa, startIndex, stopIndex, prediction, configs) 73 | -------------------------------------------------------------------------------- /pml/antlr4/tree/ParseTreePattern.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2012-2017 The ANTLR Project. All rights reserved. 3 | # Use of this file is governed by the BSD 3-clause license that 4 | # can be found in the LICENSE.txt file in the project root. 5 | # 6 | 7 | # 8 | # A pattern like {@code = ;} converted to a {@link ParseTree} by 9 | # {@link ParseTreePatternMatcher#compile(String, int)}. 10 | # 11 | from antlr4.tree.ParseTreePatternMatcher import ParseTreePatternMatcher 12 | from antlr4.tree.Tree import ParseTree 13 | from antlr4.xpath.XPath import XPath 14 | 15 | 16 | class ParseTreePattern(object): 17 | 18 | # Construct a new instance of the {@link ParseTreePattern} class. 19 | # 20 | # @param matcher The {@link ParseTreePatternMatcher} which created this 21 | # tree pattern. 22 | # @param pattern The tree pattern in concrete syntax form. 23 | # @param patternRuleIndex The parser rule which serves as the root of the 24 | # tree pattern. 25 | # @param patternTree The tree pattern in {@link ParseTree} form. 26 | # 27 | def __init__(self, matcher:ParseTreePatternMatcher, pattern:str, patternRuleIndex:int , patternTree:ParseTree): 28 | self.matcher = matcher 29 | self.patternRuleIndex = patternRuleIndex 30 | self.pattern = pattern 31 | self.patternTree = patternTree 32 | 33 | # 34 | # Match a specific parse tree against this tree pattern. 35 | # 36 | # @param tree The parse tree to match against this tree pattern. 37 | # @return A {@link ParseTreeMatch} object describing the result of the 38 | # match operation. The {@link ParseTreeMatch#succeeded()} method can be 39 | # used to determine whether or not the match was successful. 40 | # 41 | def match(self, tree:ParseTree): 42 | return self.matcher.match(tree, self) 43 | 44 | # 45 | # Determine whether or not a parse tree matches this tree pattern. 46 | # 47 | # @param tree The parse tree to match against this tree pattern. 48 | # @return {@code true} if {@code tree} is a match for the current tree 49 | # pattern; otherwise, {@code false}. 50 | # 51 | def matches(self, tree:ParseTree): 52 | return self.matcher.match(tree, self).succeeded() 53 | 54 | # Find all nodes using XPath and then try to match those subtrees against 55 | # this tree pattern. 56 | # 57 | # @param tree The {@link ParseTree} to match against this pattern. 58 | # @param xpath An expression matching the nodes 59 | # 60 | # @return A collection of {@link ParseTreeMatch} objects describing the 61 | # successful matches. Unsuccessful matches are omitted from the result, 62 | # regardless of the reason for the failure. 63 | # 64 | def findAll(self, tree:ParseTree, xpath:str): 65 | subtrees = XPath.findAll(tree, xpath, self.matcher.parser) 66 | matches = list() 67 | for t in subtrees: 68 | match = self.match(t) 69 | if match.succeeded(): 70 | matches.append(match) 71 | return matches 72 | -------------------------------------------------------------------------------- /threed_tiles/gltf_patch.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from mathutils import Vector, Quaternion, Matrix 3 | 4 | 5 | def set_convert_functions_4_5(gltf): 6 | if bpy.app.debug_value != 100: 7 | # Unit conversion factor in (Blender units) per meter 8 | u = 1.0 / bpy.context.scene.unit_settings.scale_length 9 | 10 | offset = gltf._offset 11 | # We will apply before creating a to decrease numerical errors caused by 12 | # 32 bits floats used by , and other classes. 13 | 14 | # glTF Y-Up space --> Blender Z-up space 15 | # X,Y,Z --> X,-Z,Y 16 | # 17 | # Patch 18 | # 19 | def convert_loc(x): return u * Vector([x[0] - offset[0], -x[2] - offset[1], x[1] - offset[2]]) 20 | def convert_quat(q): return Quaternion([q[3], q[0], -q[2], q[1]]) 21 | def convert_scale(s): return Vector([s[0], s[2], s[1]]) 22 | 23 | # 24 | # Patch 25 | # 26 | def convert_matrix(m): 27 | return Matrix([ 28 | [m[0], -m[8], m[4], (m[12] - offset[0]) * u], 29 | [-m[2], m[10], -m[6], (-m[14] - offset[1]) * u], 30 | [m[1], -m[9], m[5], (m[13] - offset[2]) * u], 31 | [m[3] / u, -m[11] / u, m[7] / u, m[15]], 32 | ]) 33 | 34 | # Batch versions operate in place on a numpy array 35 | def convert_locs_batch(locs): 36 | # x,y,z -> x,-z,y 37 | locs[:, [1, 2]] = locs[:, [2, 1]] 38 | locs[:, 1] *= -1 39 | # Unit conversion 40 | if u != 1: 41 | locs *= u 42 | 43 | def convert_normals_batch(ns): 44 | ns[:, [1, 2]] = ns[:, [2, 1]] 45 | ns[:, 1] *= -1 46 | 47 | # Correction for cameras and lights. 48 | # glTF: right = +X, forward = -Z, up = +Y 49 | # glTF after Yup2Zup: right = +X, forward = +Y, up = +Z 50 | # Blender: right = +X, forward = -Z, up = +Y 51 | # Need to carry Blender --> glTF after Yup2Zup 52 | gltf.camera_correction = Quaternion((2**0.5 / 2, 2**0.5 / 2, 0.0, 0.0)) 53 | 54 | else: 55 | def convert_loc(x): return Vector(x) 56 | def convert_quat(q): return Quaternion([q[3], q[0], q[1], q[2]]) 57 | def convert_scale(s): return Vector(s) 58 | 59 | def convert_matrix(m): 60 | return Matrix([m[0::4], m[1::4], m[2::4], m[3::4]]) 61 | 62 | def convert_locs_batch(_locs): return 63 | def convert_normals_batch(_ns): return 64 | 65 | # Same convention, no correction needed. 66 | gltf.camera_correction = None 67 | 68 | gltf.loc_gltf_to_blender = convert_loc 69 | gltf.locs_batch_gltf_to_blender = convert_locs_batch 70 | gltf.quaternion_gltf_to_blender = convert_quat 71 | gltf.normals_batch_gltf_to_blender = convert_normals_batch 72 | gltf.scale_gltf_to_blender = convert_scale 73 | gltf.matrix_gltf_to_blender = convert_matrix 74 | 75 | 76 | 77 | def select_imported_objects_4_1(gltf): 78 | return -------------------------------------------------------------------------------- /pml/antlr4/CommonTokenStream.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2012-2017 The ANTLR Project. All rights reserved. 3 | # Use of this file is governed by the BSD 3-clause license that 4 | # can be found in the LICENSE.txt file in the project root. 5 | #/ 6 | 7 | # 8 | # This class extends {@link BufferedTokenStream} with functionality to filter 9 | # token streams to tokens on a particular channel (tokens where 10 | # {@link Token#getChannel} returns a particular value). 11 | # 12 | #

13 | # This token stream provides access to all tokens by index or when calling 14 | # methods like {@link #getText}. The channel filtering is only used for code 15 | # accessing tokens via the lookahead methods {@link #LA}, {@link #LT}, and 16 | # {@link #LB}.

17 | # 18 | #

19 | # By default, tokens are placed on the default channel 20 | # ({@link Token#DEFAULT_CHANNEL}), but may be reassigned by using the 21 | # {@code ->channel(HIDDEN)} lexer command, or by using an embedded action to 22 | # call {@link Lexer#setChannel}. 23 | #

24 | # 25 | #

26 | # Note: lexer rules which use the {@code ->skip} lexer command or call 27 | # {@link Lexer#skip} do not produce tokens at all, so input text matched by 28 | # such a rule will not be available as part of the token stream, regardless of 29 | # channel.

30 | #/ 31 | 32 | from antlr4.BufferedTokenStream import BufferedTokenStream 33 | from antlr4.Lexer import Lexer 34 | from antlr4.Token import Token 35 | 36 | 37 | class CommonTokenStream(BufferedTokenStream): 38 | 39 | def __init__(self, lexer:Lexer, channel:int=Token.DEFAULT_CHANNEL): 40 | super().__init__(lexer) 41 | self.channel = channel 42 | 43 | def adjustSeekIndex(self, i:int): 44 | return self.nextTokenOnChannel(i, self.channel) 45 | 46 | def LB(self, k:int): 47 | if k==0 or (self.index-k)<0: 48 | return None 49 | i = self.index 50 | n = 1 51 | # find k good tokens looking backwards 52 | while n <= k: 53 | # skip off-channel tokens 54 | i = self.previousTokenOnChannel(i - 1, self.channel) 55 | n += 1 56 | if i < 0: 57 | return None 58 | return self.tokens[i] 59 | 60 | def LT(self, k:int): 61 | self.lazyInit() 62 | if k == 0: 63 | return None 64 | if k < 0: 65 | return self.LB(-k) 66 | i = self.index 67 | n = 1 # we know tokens[pos] is a good one 68 | # find k good tokens 69 | while n < k: 70 | # skip off-channel tokens, but make sure to not look past EOF 71 | if self.sync(i + 1): 72 | i = self.nextTokenOnChannel(i + 1, self.channel) 73 | n += 1 74 | return self.tokens[i] 75 | 76 | # Count EOF just once.#/ 77 | def getNumberOfOnChannelTokens(self): 78 | n = 0 79 | self.fill() 80 | for i in range(0, len(self.tokens)): 81 | t = self.tokens[i] 82 | if t.channel==self.channel: 83 | n += 1 84 | if t.type==Token.EOF: 85 | break 86 | return n 87 | -------------------------------------------------------------------------------- /item_renderer/texture/roof_flat_multi.py: -------------------------------------------------------------------------------- 1 | import bmesh 2 | from .. import ItemRenderer 3 | from util import zAxis 4 | 5 | 6 | def _getEdge(bmVert): 7 | return ( 8 | bmVert.link_loops[0]\ 9 | if bmVert.co[2] > bmVert.link_loops[1].link_loop_next.vert.co[2] else\ 10 | bmVert.link_loops[1] 11 | ).edge 12 | 13 | 14 | class RoofFlatMulti(ItemRenderer): 15 | 16 | def render(self, roofItem): 17 | building = roofItem.building 18 | # all needed BMesh verts have been already created during facade generation 19 | bmVerts = building.bmVerts 20 | 21 | # create a Python list of BMesh edges for the outer and inner polygons 22 | indexOffset = roofItem.firstVertIndex 23 | # treat the outer polygon 24 | polygon = roofItem.footprint.polygon 25 | edges = [_getEdge(bmVerts[i]) for i in range(indexOffset, indexOffset + polygon.n) ] 26 | 27 | # treat the inner polygons 28 | indexOffset += polygon.n 29 | for polygon in roofItem.innerPolygons: 30 | # skipping the verts for the lower cap 31 | indexOffset += polygon.n 32 | edges.extend(_getEdge(bmVerts[i]) for i in range(indexOffset, indexOffset + polygon.n)) 33 | # skipping the verts for the upper cap 34 | indexOffset += polygon.n 35 | 36 | # a magic function that does everything 37 | self.renderCladding( 38 | roofItem, 39 | tuple( 40 | face for face in bmesh.ops.triangle_fill(self.r.bm, use_beauty=False, use_dissolve=False, edges=edges)\ 41 | ["geom"] if isinstance(face, bmesh.types.BMFace) 42 | ), 43 | None 44 | ) 45 | 46 | def setCladdingUvs(self, roofItem, faces, claddingTextureInfo, uvs): 47 | textureWidthM = claddingTextureInfo["textureWidthM"] 48 | textureHeightM = textureWidthM * claddingTextureInfo["textureSize"][1] / claddingTextureInfo["textureSize"][0] 49 | 50 | polygon = roofItem.footprint.polygon 51 | verts = polygon.allVerts 52 | indices = polygon.indices 53 | 54 | # Arrange the texture along the longest edge of , 55 | # so the longest edges surves as u-axis for the texture 56 | maxEdgeIndex = polygon.maxEdgeIndex 57 | offset = verts[indices[maxEdgeIndex]] 58 | uVec = (verts[indices[maxEdgeIndex+1]] - offset) 59 | uVec.normalize() 60 | vVec = zAxis.cross(uVec) 61 | 62 | for face in faces: 63 | self.r.setUvs( 64 | face, 65 | # a generator! 66 | ( 67 | ( 68 | (vert.co-offset).dot(uVec)/textureWidthM, 69 | (vert.co-offset).dot(vVec)/textureHeightM 70 | ) for vert in face.verts 71 | ), 72 | self.r.layer.uvLayerNameCladding 73 | ) 74 | 75 | def setMaterial(self, faces, materialId): 76 | for face in faces: 77 | self.r.setMaterial(face, materialId) -------------------------------------------------------------------------------- /item_renderer/texture/export/container.py: -------------------------------------------------------------------------------- 1 | import os 2 | import bpy 3 | from .item_renderer import ItemRendererMixin, _textureDir 4 | from ..container import Container as ContainerBase 5 | from ...util import setTextureSize, getPath 6 | 7 | 8 | class Container(ContainerBase, ItemRendererMixin): 9 | """ 10 | The base class for the item renderers Facade, Div, Layer, Bottom 11 | """ 12 | 13 | def __init__(self, exportMaterials): 14 | super().__init__(exportMaterials) 15 | # The following variable is used to cache the cladding color as as string: 16 | # either a base colors (e.g. red, green) or a hex string 17 | self.claddingColor = None 18 | 19 | def getFacadeMaterialId(self, item, facadeTextureInfo, claddingTextureInfo): 20 | # set UV-coordinates for the cladding texture 21 | if claddingTextureInfo: 22 | color = self.getCladdingColorHex(item) 23 | return "%s_%s_%s" % (claddingTextureInfo["name"], color, facadeTextureInfo["name"]) 24 | elif self.r.useCladdingColor and facadeTextureInfo.get("claddingColor"): 25 | color = self.getCladdingColorHex(item) 26 | return "%s_%s" % (color, facadeTextureInfo["name"]) 27 | else: 28 | return facadeTextureInfo["name"] 29 | 30 | def makeTexture(self, item, textureFilename, textureDir, textureFilepath, textColor, facadeTextureInfo, claddingTextureInfo, uvs): 31 | textureExporter = self.r.textureExporter 32 | scene = textureExporter.getTemplateScene("compositing_facade_cladding_color") 33 | nodes = textureExporter.makeCommonPreparations( 34 | scene, 35 | textureFilename, 36 | textureDir 37 | ) 38 | # facade texture 39 | image = textureExporter.setImage( 40 | facadeTextureInfo["name"], 41 | getPath(self.r, facadeTextureInfo["path"]), 42 | nodes, 43 | "facade_texture" 44 | ) 45 | setTextureSize(facadeTextureInfo, image) 46 | 47 | if claddingTextureInfo: 48 | # cladding texture 49 | image = textureExporter.setImage( 50 | claddingTextureInfo["name"], 51 | getPath(self.r, claddingTextureInfo["path"]), 52 | nodes, 53 | "cladding_texture" 54 | ) 55 | setTextureSize(claddingTextureInfo, image) 56 | # scale for the cladding texture 57 | scaleFactor = claddingTextureInfo["textureWidthM"]/\ 58 | claddingTextureInfo["textureSize"][0]*\ 59 | ( 60 | facadeTextureInfo["textureSize"][0]/item.width 61 | if "class" in facadeTextureInfo else 62 | (facadeTextureInfo["featureRpx"]-facadeTextureInfo["featureLpx"])/facadeTextureInfo["featureWidthM"] 63 | ) 64 | textureExporter.setScaleNode(nodes, "Scale", scaleFactor, scaleFactor) 65 | # cladding color 66 | textureExporter.setColor(textColor, nodes, "cladding_color") 67 | # render the resulting texture 68 | textureExporter.renderTexture(scene, textureFilepath) -------------------------------------------------------------------------------- /parse/gpx/__init__.py: -------------------------------------------------------------------------------- 1 | import xml.etree.cElementTree as etree 2 | 3 | 4 | class Gpx: 5 | """ 6 | Representation of data in a GPX file 7 | """ 8 | 9 | def __init__(self, app): 10 | self.app = app 11 | 12 | self.projection = None 13 | 14 | # a list of track segments (trkseg) 15 | self.segments = [] 16 | 17 | # the variable below is used for the bounds calculation 18 | self.firstPoint = True 19 | 20 | self.minLat = 0. 21 | self.maxLat = 0. 22 | self.minLon = 0. 23 | self.maxLon = 0. 24 | 25 | def parse(self, filepath): 26 | 27 | projection = self.app.projection 28 | 29 | self.firstPoint = True 30 | 31 | gpx = etree.parse(filepath).getroot() 32 | 33 | for e1 in gpx: # e stands for element 34 | # Each tag may have the form {http://www.topografix.com/GPX/1/1}tag 35 | # That's whay we skip curly brackets 36 | if e1.tag[e1.tag.find("}")+1:] == "trk": 37 | for e2 in e1: 38 | if e2.tag[e2.tag.find("}")+1:] == "trkseg": 39 | segment = [] 40 | for e3 in e2: 41 | if e3.tag[e3.tag.find("}")+1:] == "trkpt": 42 | lat = float(e3.attrib["lat"]) 43 | lon = float(e3.attrib["lon"]) 44 | 45 | if not projection: 46 | self.updateBounds(lat, lon) 47 | # check if has 48 | ele = None 49 | for e4 in e3: 50 | if e4.tag[e4.tag.find("}")+1:] == "ele": 51 | ele = e4 52 | break 53 | point = (lat, lon, float(ele.text)) if not ele is None else (lat, lon, 0.) 54 | segment.append(point) 55 | self.segments.append(segment) 56 | 57 | if not projection: 58 | # set projection using the calculated bounds (self.minLat, self.maxLat, self.minLon, self.maxLon) 59 | self.setProjection( 60 | (self.minLat + self.maxLat)/2., 61 | (self.minLon + self.maxLon)/2. 62 | ) 63 | 64 | def updateBounds(self, lat, lon): 65 | if self.firstPoint: 66 | self.minLat = self.maxLat = lat 67 | self.minLon = self.maxLon = lon 68 | self.firstPoint = False 69 | else: 70 | if lat < self.minLat: 71 | self.minLat = lat 72 | elif lat > self.maxLat: 73 | self.maxLat = lat 74 | if lon < self.minLon: 75 | self.minLon = lon 76 | elif lon > self.maxLon: 77 | self.maxLon = lon 78 | 79 | def setProjection(self, lat, lon): 80 | self.lat = lat 81 | self.lon = lon 82 | self.app.setProjection(lat, lon) -------------------------------------------------------------------------------- /pml/antlr4/InputStream.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2012-2017 The ANTLR Project. All rights reserved. 3 | # Use of this file is governed by the BSD 3-clause license that 4 | # can be found in the LICENSE.txt file in the project root. 5 | # 6 | import unittest 7 | 8 | 9 | # 10 | # Vacuum all input from a string and then treat it like a buffer. 11 | # 12 | from antlr4.Token import Token 13 | 14 | 15 | class InputStream (object): 16 | 17 | def __init__(self, data: str): 18 | self.name = "" 19 | self.strdata = data 20 | self._loadString() 21 | 22 | def _loadString(self): 23 | self._index = 0 24 | self.data = [ord(c) for c in self.strdata] 25 | self._size = len(self.data) 26 | 27 | @property 28 | def index(self): 29 | return self._index 30 | 31 | @property 32 | def size(self): 33 | return self._size 34 | 35 | # Reset the stream so that it's in the same state it was 36 | # when the object was created *except* the data array is not 37 | # touched. 38 | # 39 | def reset(self): 40 | self._index = 0 41 | 42 | def consume(self): 43 | if self._index >= self._size: 44 | assert self.LA(1) == Token.EOF 45 | raise Exception("cannot consume EOF") 46 | self._index += 1 47 | 48 | def LA(self, offset: int): 49 | if offset==0: 50 | return 0 # undefined 51 | if offset<0: 52 | offset += 1 # e.g., translate LA(-1) to use offset=0 53 | pos = self._index + offset - 1 54 | if pos < 0 or pos >= self._size: # invalid 55 | return Token.EOF 56 | return self.data[pos] 57 | 58 | def LT(self, offset: int): 59 | return self.LA(offset) 60 | 61 | # mark/release do nothing; we have entire buffer 62 | def mark(self): 63 | return -1 64 | 65 | def release(self, marker: int): 66 | pass 67 | 68 | # consume() ahead until p==_index; can't just set p=_index as we must 69 | # update line and column. If we seek backwards, just set p 70 | # 71 | def seek(self, _index: int): 72 | if _index<=self._index: 73 | self._index = _index # just jump; don't update stream state (line, ...) 74 | return 75 | # seek forward 76 | self._index = min(_index, self._size) 77 | 78 | def getText(self, start :int, stop: int): 79 | if stop >= self._size: 80 | stop = self._size-1 81 | if start >= self._size: 82 | return "" 83 | else: 84 | return self.strdata[start:stop+1] 85 | 86 | def __str__(self): 87 | return self.strdata 88 | 89 | 90 | class TestInputStream(unittest.TestCase): 91 | 92 | def testStream(self): 93 | stream = InputStream("abcde") 94 | self.assertEqual(0, stream.index) 95 | self.assertEqual(5, stream.size) 96 | self.assertEqual(ord("a"), stream.LA(1)) 97 | stream.consume() 98 | self.assertEqual(1, stream.index) 99 | stream.seek(5) 100 | self.assertEqual(Token.EOF, stream.LA(1)) 101 | self.assertEqual("bcd", stream.getText(1, 3)) 102 | stream.reset() 103 | self.assertEqual(0, stream.index) 104 | 105 | -------------------------------------------------------------------------------- /mpl/renderer.py: -------------------------------------------------------------------------------- 1 | import parse 2 | from . import Mpl 3 | 4 | 5 | class Renderer: 6 | 7 | def __init__(self): 8 | self.mpl = Mpl.getMpl() 9 | 10 | def renderLineString(self, coords, closed, style): 11 | prevCoord = coord0 = None 12 | for coord in coords: 13 | if prevCoord: 14 | self.mpl.ax.plot( 15 | (prevCoord[0], coord[0]), 16 | (prevCoord[1], coord[1]), 17 | **style 18 | ) 19 | elif closed: 20 | coord0 = coord 21 | prevCoord = coord 22 | if closed: 23 | self.mpl.ax.plot( 24 | (coord[0], coord0[0]), 25 | (coord[1], coord0[1]), 26 | **style 27 | ) 28 | 29 | def prepare(self): 30 | pass 31 | 32 | def finalize(self): 33 | self.mpl.show() 34 | 35 | def cleanup(self): 36 | self.mpl = None 37 | Mpl.cleanup() 38 | 39 | 40 | class WayRenderer(Renderer): 41 | """ 42 | A renderer for physical ways 43 | """ 44 | 45 | style = dict( 46 | linewidth = 1., 47 | color = "brown" 48 | ) 49 | 50 | def render(self, way, data): 51 | self.renderLineString(way.element.getData(data), way.element.isClosed(), WayRenderer.style) 52 | 53 | 54 | class BuildingRenderer(Renderer): 55 | 56 | style = dict( 57 | linewidth = 1., 58 | color = "gray" 59 | ) 60 | 61 | def render(self, building, data): 62 | if building.outline.t is parse.polygon: 63 | self.renderLineString(building.outline.getData(data), True, BuildingRenderer.style) 64 | else: 65 | # multipolygon 66 | for coords in building.outline.getDataMulti(data): 67 | self.renderLineString(coords, True, BuildingRenderer.style) 68 | 69 | 70 | class BuildingVisibilityRender(Renderer): 71 | 72 | def render(self, building, data): 73 | if building.outline.t is parse.polygon: 74 | self.renderBuildingFootprint(building) 75 | else: 76 | # multipolygon 77 | for coords in building.outline.getDataMulti(data): 78 | pass 79 | 80 | def renderBuildingFootprint(self, building): 81 | polygon = building.polygon 82 | allVerts = polygon.allVerts 83 | indices = polygon.indices 84 | 85 | for edgeIndex in range(polygon.n-1): 86 | vert1 = allVerts[indices[edgeIndex]] 87 | vert2 = allVerts[indices[edgeIndex+1]] 88 | visibility = building.visibility[0][indices[edgeIndex]] 89 | self.mpl.ax.plot( 90 | (vert1[0], vert2[0]), 91 | (vert1[1], vert2[1]), 92 | linewidth = 1., 93 | color = 'green' if visibility else 'red' 94 | ) 95 | 96 | vert1 = allVerts[indices[-1]] 97 | vert2 = allVerts[indices[0]] 98 | visibility = building.visibility[0][indices[-1]] 99 | self.mpl.ax.plot( 100 | (vert1[0], vert2[0]), 101 | (vert1[1], vert2[1]), 102 | linewidth = 1., 103 | color = 'green' if visibility else 'red' 104 | ) -------------------------------------------------------------------------------- /item_renderer/texture/base/door.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from .container import Container 3 | from ..door import Door as DoorBase 4 | from ....util.blender_extra.material import createMaterialFromTemplate, setImage 5 | from ...util import getPath 6 | 7 | 8 | class Door(DoorBase, Container): 9 | 10 | def __init__(self): 11 | # a reference to the Container class used in the parent classes 12 | self.Container = Container 13 | Container.__init__(self, exportMaterials=False) 14 | DoorBase.__init__(self) 15 | 16 | def renderLevelGroup(self, parentItem, levelGroup, indices, uvs): 17 | face = self.r.createFace(parentItem.building, indices) 18 | item = levelGroup.item 19 | if item.materialId is None: 20 | self.setMaterialId( 21 | item, 22 | parentItem.building, 23 | # building part 24 | "door", 25 | uvs 26 | ) 27 | if item.materialId: 28 | facadeTextureInfo, claddingTextureInfo = item.materialData 29 | faceWidth = uvs[1][0] - uvs[0][0] 30 | faceHeight = uvs[2][1] - uvs[1][1] 31 | doorWidth = facadeTextureInfo["textureWidthM"] 32 | doorHeight = facadeTextureInfo["textureHeightM"] 33 | u1 = 0.5 - 0.5*faceWidth/doorWidth 34 | u2 = 1. - u1 35 | v = faceHeight/doorHeight 36 | self.r.setUvs( 37 | face, 38 | # we assume that the face is a rectangle 39 | ( 40 | (u1, 0.), (u2, 0.), (u2, v), (u1, v) 41 | ), 42 | self.r.layer.uvLayerNameFacade 43 | ) 44 | # set UV-coordinates for the cladding texture 45 | self.setCladdingUvs(item, face, claddingTextureInfo, uvs) 46 | self.setVertexColor(item, face) 47 | self.r.setMaterial(face, item.materialId) 48 | 49 | def createFacadeMaterial(self, item, materialName, facadeTextureInfo, claddingTextureInfo, uvs): 50 | if not materialName in bpy.data.materials: 51 | materialTemplate = self.getFacadeMaterialTemplate( 52 | facadeTextureInfo, 53 | claddingTextureInfo 54 | ) 55 | nodes = createMaterialFromTemplate(materialTemplate, materialName) 56 | # the overlay texture 57 | setImage( 58 | facadeTextureInfo["name"], 59 | getPath(self.r, facadeTextureInfo["path"]), 60 | nodes, 61 | "Main" 62 | ) 63 | if claddingTextureInfo: 64 | # The wall material (i.e. background) texture, 65 | # set it just in case 66 | setImage( 67 | claddingTextureInfo["name"], 68 | getPath(self.r, claddingTextureInfo["path"]), 69 | nodes, 70 | "Cladding" 71 | ) 72 | return True 73 | 74 | def getFacadeMaterialTemplate(self, facadeTextureInfo, claddingTextureInfo): 75 | if claddingTextureInfo: 76 | materialTemplateName = "door_cladding_color" if self.r.useCladdingColor else "door_cladding" 77 | else: 78 | materialTemplateName = "export" 79 | return self.getMaterialTemplate(materialTemplateName) -------------------------------------------------------------------------------- /item/roof_side.py: -------------------------------------------------------------------------------- 1 | from . import Item 2 | 3 | 4 | _className = "RoofSide" 5 | 6 | 7 | class RoofSide(Item): 8 | 9 | def __init__(self): 10 | super().__init__() 11 | self.buildingPart = "roof_side" 12 | # slot index for the profile roofs, edge index for hipped roofs 13 | self.itemIndex = 0 14 | # indices of that form the roof side 15 | self.indices = None 16 | 17 | @classmethod 18 | def getItem(cls, itemFactory, parent, indices, uvs, itemIndex): 19 | """ 20 | Args: 21 | itemIndex (int): for , for 22 | """ 23 | item = itemFactory.getItem(cls) 24 | item.init() 25 | item.parent = parent 26 | item.footprint = parent.footprint 27 | item.setStyleBlock() 28 | item.building = parent.building 29 | item.indices = indices 30 | item.uvs = uvs 31 | item.itemIndex = itemIndex 32 | return item 33 | 34 | def setStyleBlock(self): 35 | # The logic for setting a style block for the roof side is the following: 36 | # (1) If has a markup (actually ), then search the markup of 37 | # the related style block for style block. 38 | # If the style block wasn't found, then stop there and 39 | # it means that there is no style block. 40 | # (2) If does not have a markup (actually ), 41 | # then search for style blocks in the markup 42 | # (actually in ) of the related footprint, 43 | # also try to find them at the very top of the style definitions 44 | 45 | # is an instance of style blocks in in the markup (actually ) of the roof item 50 | styleBlocks = roofStyleBlock.styleBlocks[_className] 51 | else: 52 | footprint = self.footprint 53 | # Find style blocks in (actually in ), 54 | # also try to find them at the very top of the style definitions 55 | styleBlocks = footprint.styleBlock.styleBlocks.get( 56 | _className, 57 | footprint.buildingStyle.styleBlocks.get(_className) 58 | ) 59 | if styleBlocks: 60 | for styleBlock in styleBlocks: 61 | if self.evaluateCondition(styleBlock): 62 | self.styleBlock = styleBlock 63 | 64 | @property 65 | def front(self): 66 | return True 67 | 68 | @property 69 | def back(self): 70 | return True 71 | 72 | def getStyleBlockAttr(self, attr): 73 | value = super().getStyleBlockAttr(attr) if self.styleBlock else None 74 | return value or self.parent.getStyleBlockAttr(attr) 75 | 76 | def getCladdingMaterial(self): 77 | return self.getStyleBlockAttr("roofCladdingMaterial") 78 | 79 | def getCladdingColor(self): 80 | return self.getStyleBlockAttr("roofCladdingColor") -------------------------------------------------------------------------------- /renderer/curve_layer.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of Blosm addon for Blender. 3 | Copyright (C) 2014-2018 Vladimir Elistratov 4 | prokitektura+support@gmail.com 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | import os 21 | import bpy 22 | from .layer import MeshLayer 23 | from util.blender import appendObjectsFromFile, createDiffuseMaterial, createCollection, addShrinkwrapModifier 24 | 25 | _isBlender291 = bpy.app.version[1] >= 91 26 | 27 | 28 | class CurveLayer(MeshLayer): 29 | 30 | # Blender layer index to place a way profile 31 | profileLayerIndex = 1 32 | 33 | # Blender file with way profiles 34 | assetFile = "way_profiles.blend" 35 | 36 | collectionName = "way_profiles" 37 | 38 | def __init__(self, layerId, app): 39 | super().__init__(layerId, app) 40 | self.assetPath = os.path.join(app.assetPath, self.assetFile) 41 | 42 | def getDefaultZ(self, app): 43 | return app.wayZ 44 | 45 | def getDefaultSwOffset(self, app): 46 | return app.swWayOffset 47 | 48 | def finalizeBlenderObject(self, obj): 49 | """ 50 | Slice Blender MESH object, add modifiers 51 | """ 52 | # set a bevel object for the curve 53 | curve = obj.data 54 | # the name of the bevel object 55 | bevelName = "profile_%s" % self.id 56 | bevelObj = bpy.data.objects.get(bevelName) 57 | if not (bevelObj and bevelObj.type == 'CURVE'): 58 | bevelObj = appendObjectsFromFile(self.assetPath, None, bevelName)[0] 59 | if bevelObj: 60 | collection = bpy.data.collections.get(self.collectionName) 61 | if not collection: 62 | collection = createCollection( 63 | self.collectionName, 64 | hide_viewport=True, 65 | hide_select=True, 66 | hide_render=True 67 | ) 68 | collection.objects.link(bevelObj) 69 | bevelObj.hide_viewport = True 70 | bevelObj.hide_select = True 71 | bevelObj.hide_render = True 72 | if bevelObj and bevelObj.type == 'CURVE': 73 | curve.bevel_object = bevelObj 74 | if _isBlender291: 75 | curve.bevel_mode = 'OBJECT' 76 | # set a material 77 | # the material name is simply of the layer 78 | name = self.id 79 | material = bpy.data.materials.get(name) 80 | curve.materials.append( 81 | material or createDiffuseMaterial(name, self.app.colors.get(name, self.app.defaultColor)) 82 | ) 83 | 84 | if self.modifiers: 85 | addShrinkwrapModifier(obj, self.app.terrain.terrain, self.swOffset) -------------------------------------------------------------------------------- /item_renderer/texture/facade.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Facade: 4 | 5 | def init(self, itemRenderers, globalRenderer): 6 | self.Container.init(self, itemRenderers, globalRenderer) 7 | self.bottomRenderer = itemRenderers["Bottom"] 8 | 9 | def render(self, footprint, data): 10 | # is the global building renderer 11 | r = self.r 12 | building = footprint.building 13 | 14 | if footprint.building.classifyFacades and not footprint.noWalls: 15 | footprint.classifyFacades(data) 16 | 17 | facadeStyle = footprint.facadeStyle 18 | if footprint.facadeStyle: 19 | for facade in footprint.facades: 20 | for styleBlock in facadeStyle: 21 | if facade.evaluateCondition(styleBlock): 22 | facade.styleBlock = styleBlock 23 | # check if attribute is given in the styleBlock 24 | minWidth = facade.getStyleBlockAttr("minWidth") 25 | if (minWidth and facade.width > minWidth) or not minWidth: 26 | facadeClass = facade.getStyleBlockAttr("cl") 27 | if facadeClass: 28 | self.renderClass( 29 | facade, 30 | facadeClass, 31 | r.createFace(building, facade.indices), 32 | facade.uvs 33 | ) 34 | break 35 | elif styleBlock.markup: 36 | self.renderMarkup(facade) 37 | if facade.valid: 38 | break 39 | else: 40 | # does not suit for 41 | # Make valid again to try it 42 | # with the next from 43 | facade.valid = True 44 | facade.markup.clear() 45 | else: 46 | # No markup, so we render cladding only. 47 | self.renderCladding( 48 | facade, 49 | r.createFace(building, facade.indices), 50 | facade.uvs 51 | ) 52 | break 53 | # Clean up the styleBlock for the next attempt with 54 | # the next style block from 55 | facade.styleBlock = None 56 | else: 57 | # No style block suits the 58 | # Use style of to render cladding for 59 | self.renderCladding( 60 | footprint, 61 | r.createFace(building, facade.indices), 62 | facade.uvs 63 | ) 64 | else: 65 | # simply create BMFaces here 66 | for facade in footprint.facades: 67 | r.createFace(building, facade.indices) -------------------------------------------------------------------------------- /setup/premium.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of Blosm addon for Blender. 3 | Copyright (C) 2014-2018 Vladimir Elistratov 4 | prokitektura+support@gmail.com 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | from ..parse.osm.relation.building import Building 21 | 22 | from ..building.manager import BuildingParts, BuildingRelations 23 | 24 | from ..manager.logging import Logger 25 | 26 | from ..building.manager import BuildingManager 27 | from ..realistic.building.layer import RealisticBuildingLayer 28 | from ..realistic.building.renderer import RealisticBuildingRenderer 29 | 30 | 31 | def setup_base(app, osm, getMaterials, bldgPreRender): 32 | # comment the next line if logging isn't needed 33 | Logger(app, osm) 34 | 35 | if app.buildings: 36 | buildingParts = BuildingParts() 37 | buildingRelations = BuildingRelations() 38 | buildings = BuildingManager(osm, app, buildingParts, RealisticBuildingLayer) 39 | 40 | # Important: beform , 41 | # since there may be a tag building=* in an OSM relation of the type 'building' 42 | osm.addCondition( 43 | lambda tags, e: isinstance(e, Building), 44 | None, 45 | buildingRelations 46 | ) 47 | osm.addCondition( 48 | lambda tags, e: "building" in tags, 49 | "buildings", 50 | buildings 51 | ) 52 | osm.addCondition( 53 | lambda tags, e: "building:part" in tags, 54 | None, 55 | buildingParts 56 | ) 57 | # set building renderer 58 | br = RealisticBuildingRenderer( 59 | app, 60 | bldgPreRender = bldgPreRender, 61 | materials = getMaterials() 62 | ) 63 | #
stands for "building renderer" 64 | buildings.setRenderer(br) 65 | 66 | if app.forests: 67 | setup_forests(app, osm) 68 | 69 | 70 | def setup_forests(app, osm): 71 | from ..renderer import Renderer2d 72 | from ..realistic.manager import AreaManager 73 | from ..realistic.renderer import AreaRenderer, ForestRenderer 74 | from ..renderer.node_renderer import SingleTreeRenderer 75 | 76 | areaRenderers = dict(forest=ForestRenderer()) 77 | # create managers 78 | m = AreaManager(osm, app, AreaRenderer(), **areaRenderers) 79 | 80 | osm.addCondition( 81 | lambda tags, e: tags.get("natural") == "wood" or tags.get("landuse") == "forest", 82 | "forest", 83 | m 84 | ) 85 | 86 | osm.addNodeCondition( 87 | lambda tags, e: tags.get("natural") == "tree", 88 | "trees", 89 | None, 90 | SingleTreeRenderer(app) 91 | ) 92 | 93 | m.setRenderer(Renderer2d(app, applyMaterial=False)) 94 | app.addManager(m) -------------------------------------------------------------------------------- /item/__init__.py: -------------------------------------------------------------------------------- 1 | from grammar import perBuilding 2 | 3 | 4 | class Item: 5 | 6 | def __init__(self): 7 | self.valid = True 8 | # for example, a parent for a facade is a footprint 9 | self.parent = None 10 | # a direct access to the footprint 11 | self.footprint = None 12 | # A style block (an instance of grammar.Item) that defines the style for the item 13 | # within a markup definition. 14 | # Typically a style block is defined in the markup definition, however it can be also defined 15 | # at the very top if the style definition for the item Footprint, Facade, RoofSide, Ridge, Roof 16 | self.styleBlock = None 17 | self.width = None 18 | self.relativeWidth = None 19 | self.hasFlexWidth = False 20 | # the following variable is used to cache a material id (e.g a string name) 21 | self.materialId = None 22 | # Python dictionary to cache attributes from that are derived 23 | # from 24 | self._cache = {} 25 | 26 | def init(self): 27 | self.valid = True 28 | self.parent = None 29 | self.footprint = None 30 | self.styleBlock = None 31 | self.width = None 32 | self.relativeWidth = None 33 | self.hasFlexWidth = False 34 | self.materialId = None 35 | self._cache.clear() 36 | 37 | def evaluateCondition(self, styleBlock): 38 | return not styleBlock.condition or styleBlock.condition(self) 39 | 40 | def getStyleBlockAttr(self, attr): 41 | attrs = self.styleBlock.attrs 42 | if not attr in attrs: 43 | return 44 | value, isComplexValue = attrs.get(attr) 45 | if isComplexValue: 46 | if attr in self._cache: 47 | return self._cache[attr] 48 | value = value.getValue(self) 49 | # There is no need to perform a possibly complex calculations of the complex value once again, 50 | # so we simply store the resulting value in the cache 51 | self._cache[attr] = value 52 | return value 53 | 54 | def getStyleBlockAttrDeep(self, attr): 55 | if self.styleBlock and attr in self.styleBlock.attrs: 56 | return self.getStyleBlockAttr(attr) 57 | elif attr in self._cache: 58 | return self._cache[attr] 59 | else: 60 | # try to get the attribute from 61 | value = self.parent.getStyleBlockAttrDeep(attr) 62 | self._cache[attr] = value 63 | return value 64 | 65 | def getCache(self, scope): 66 | return self.building._cache if scope is perBuilding else self._cache 67 | 68 | def getItemRenderer(self, itemRenderers): 69 | """ 70 | Get a renderer for the item contained in the markup. 71 | """ 72 | return itemRenderers[self.__class__.__name__] 73 | 74 | def clone(self): 75 | item = self.__class__() 76 | # set item factory to be used inside 77 | item.itemFactory = self.itemFactory 78 | return item 79 | 80 | def getMargin(self): 81 | return 0. 82 | 83 | def getCladdingMaterial(self): 84 | return self.getStyleBlockAttrDeep("claddingMaterial") 85 | 86 | def getCladdingColor(self): 87 | return self.getStyleBlockAttrDeep("claddingColor") -------------------------------------------------------------------------------- /realistic/building/renderer.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is a part of Blosm addon for Blender. 3 | Copyright (C) 2014-2018 Vladimir Elistratov 4 | prokitektura+support@gmail.com 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | from ...building.renderer import * 21 | 22 | from .roof.flat import RoofFlatRealistic, RoofFlatMultiRealistic 23 | from .roof.profile import RoofProfileRealistic 24 | from .roof.pyramidal import RoofPyramidalRealistic 25 | from .roof.skillion import RoofSkillionRealistic 26 | from .roof.hipped import RoofHippedRealistic 27 | from .roof.half_hipped import RoofHalfHippedRealistic 28 | from .roof.mansard import RoofMansardRealistic 29 | from .roof.mesh import RoofMeshRealistic 30 | 31 | 32 | class RealisticBuildingRenderer(BuildingRenderer): 33 | 34 | def __init__(self, app, **kwargs): 35 | self.bldgPreRender = None 36 | super().__init__(app) 37 | for k in kwargs: 38 | setattr(self, k, kwargs[k]) 39 | # A Python dictionary for mapping between material names and material renderers; 40 | # it's normally set via 41 | if not self.materials: 42 | self.materials = {} 43 | # a Python dictionary of material renderers 44 | self.materialRenderers = {} 45 | # a Python set of names of processed material groups; 46 | # it maybe used by several material renderers 47 | self.materialGroups = set() 48 | 49 | def getMaterialRenderer(self, name): 50 | # stands for "material renderer" 51 | mr = self.materialRenderers.get(name) 52 | if not mr: 53 | # stands for material definition 54 | md = self.materials.get(name) 55 | if md: 56 | # is a constructor 57 | mr = md[0](self, name, *md[1:])\ 58 | if isinstance(md, tuple) else\ 59 | md(self, name) # is just a constructor 60 | self.materialRenderers[name] = mr 61 | return mr 62 | 63 | def initRoofs(self): 64 | """ 65 | The override of the parent class method 66 | """ 67 | self.flatRoofMulti = RoofFlatMultiRealistic() 68 | self.roofs = { 69 | 'flat': RoofFlatRealistic(), 70 | 'gabled': RoofProfileRealistic(gabledRoof), 71 | 'pyramidal': RoofPyramidalRealistic(), 72 | 'skillion': RoofSkillionRealistic(), 73 | 'hipped': RoofHippedRealistic(), 74 | 'dome': RoofMeshRealistic("roof_dome"), 75 | 'onion': RoofMeshRealistic("roof_onion"), 76 | 'round': RoofProfileRealistic(roundRoof), 77 | 'half-hipped': RoofHalfHippedRealistic(), 78 | 'gambrel': RoofProfileRealistic(gambrelRoof), 79 | 'saltbox': RoofProfileRealistic(saltboxRoof), 80 | 'mansard': RoofMansardRealistic() 81 | } -------------------------------------------------------------------------------- /item_renderer/texture/base/item_renderer.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from ....util.blender_extra.material import createMaterialFromTemplate, setImage 3 | from ...util import setTextureSize, setTextureSize2, getPath 4 | 5 | 6 | _claddingMaterialTemplateName = "tiles_color" 7 | 8 | 9 | class ItemRendererMixin: 10 | """ 11 | A mixin class 12 | """ 13 | 14 | def getCladdingMaterialId(self, item, claddingTextureInfo): 15 | return claddingTextureInfo["name"] 16 | 17 | def createCladdingMaterial(self, materialName, claddingTextureInfo): 18 | materialTemplate = self.getMaterialTemplate( 19 | _claddingMaterialTemplateName 20 | ) 21 | if not materialName in bpy.data.materials: 22 | nodes = createMaterialFromTemplate(materialTemplate, materialName) 23 | # The wall material (i.e. background) texture, 24 | # set it just in case 25 | image = setImage( 26 | claddingTextureInfo["name"], 27 | getPath(self.r, claddingTextureInfo["path"]), 28 | nodes, 29 | "Cladding" 30 | ) 31 | setTextureSize(claddingTextureInfo, image) 32 | 33 | setTextureSize2(claddingTextureInfo, materialName, "Cladding") 34 | # return True for consistency with 35 | return True 36 | 37 | def setVertexColor(self, item, face): 38 | color = item.getCladdingColor() 39 | if color: 40 | self.r.setVertexColor(face, color, self.r.layer.vertexColorLayerNameCladding) 41 | 42 | def getCladdingTextureInfo(self, item): 43 | return self._getCladdingTextureInfo(item) 44 | 45 | def createFacadeMaterial(self, item, materialName, facadeTextureInfo, claddingTextureInfo, uvs): 46 | if not materialName in bpy.data.materials: 47 | materialTemplate = self.getFacadeMaterialTemplate( 48 | facadeTextureInfo, 49 | claddingTextureInfo 50 | ) 51 | nodes = createMaterialFromTemplate(materialTemplate, materialName) 52 | # the overlay texture 53 | image = setImage( 54 | facadeTextureInfo["name"], 55 | getPath(self.r, facadeTextureInfo["path"]), 56 | nodes, 57 | "Main" 58 | ) 59 | setTextureSize(facadeTextureInfo, image) 60 | 61 | if claddingTextureInfo: 62 | # The wall material (i.e. background) texture, 63 | # set it just in case 64 | image = setImage( 65 | claddingTextureInfo["name"], 66 | getPath(self.r, claddingTextureInfo["path"]), 67 | nodes, 68 | "Cladding" 69 | ) 70 | setTextureSize(claddingTextureInfo, image) 71 | 72 | setTextureSize2(facadeTextureInfo, materialName, "Main") 73 | if claddingTextureInfo: 74 | setTextureSize2(claddingTextureInfo, materialName, "Cladding") 75 | return True 76 | 77 | def renderExtra(self, item, face, facadeTextureInfo, claddingTextureInfo, uvs): 78 | # set UV-coordinates for the cladding texture 79 | if claddingTextureInfo: 80 | self.setCladdingUvs(item, face, claddingTextureInfo, uvs) 81 | self.setVertexColor(item, face) 82 | elif self.r.useCladdingColor and facadeTextureInfo.get("claddingColor"): 83 | self.setVertexColor(item, face) -------------------------------------------------------------------------------- /threed_tiles/py3dtiles/typing.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import ( 4 | TYPE_CHECKING, 5 | Any, 6 | Dict, 7 | List, 8 | Literal, 9 | Optional, 10 | Tuple, 11 | TypedDict, 12 | Union, 13 | ) 14 | 15 | import numpy as np 16 | import numpy.typing as npt 17 | #from pyproj import CRS 18 | 19 | if TYPE_CHECKING: 20 | from typing_extensions import NotRequired 21 | 22 | # Tileset types 23 | 24 | ExtensionDictType = Dict[str, Any] 25 | ExtraDictType = Dict[str, Any] 26 | GeometricErrorType = float 27 | PropertyType = Dict[str, Any] 28 | RefineType = Literal["ADD", "REPLACE"] 29 | TransformDictType = List[float] 30 | 31 | 32 | class RootPropertyDictType(TypedDict): 33 | extensions: NotRequired[dict[str, ExtensionDictType]] 34 | extras: NotRequired[ExtraDictType] 35 | 36 | 37 | class BoundingVolumeBoxDictType(RootPropertyDictType): 38 | box: list[float] 39 | 40 | 41 | class BoundingVolumeRegionDictType(RootPropertyDictType): 42 | region: list[float] 43 | 44 | 45 | class BoundingVolumeSphereDictType(RootPropertyDictType): 46 | sphere: list[float] 47 | 48 | 49 | BoundingVolumeDictType = Union[ 50 | BoundingVolumeBoxDictType, 51 | BoundingVolumeRegionDictType, 52 | BoundingVolumeSphereDictType, 53 | ] 54 | 55 | 56 | class ContentType(RootPropertyDictType): 57 | boundingVolume: NotRequired[BoundingVolumeDictType] 58 | uri: str 59 | 60 | 61 | class PropertyDictType(RootPropertyDictType): 62 | maximum: float 63 | minimum: float 64 | 65 | 66 | class AssetDictType(RootPropertyDictType): 67 | version: Literal["1.0", "1.1"] 68 | tilesetVersion: NotRequired[str] 69 | 70 | 71 | class TileDictType(RootPropertyDictType): 72 | boundingVolume: BoundingVolumeDictType 73 | geometricError: GeometricErrorType 74 | viewerRequestVolume: NotRequired[BoundingVolumeDictType] 75 | refine: NotRequired[RefineType] 76 | transform: NotRequired[TransformDictType] 77 | content: NotRequired[ContentType] 78 | children: NotRequired[list[TileDictType]] 79 | 80 | 81 | class TilesetDictType(RootPropertyDictType): 82 | asset: AssetDictType 83 | geometricError: GeometricErrorType 84 | root: TileDictType 85 | properties: NotRequired[PropertyType] 86 | extensionsUsed: NotRequired[list[str]] 87 | extensionsRequired: NotRequired[list[str]] 88 | 89 | 90 | # Tile content types 91 | 92 | BatchTableHeaderDataType = Dict[str, Union[List[Any], Dict[str, Any]]] 93 | 94 | FeatureTableHeaderDataType = Dict[ 95 | str, 96 | Union[ 97 | int, # points_length 98 | Dict[str, int], # byte offsets 99 | Tuple[float, float, float], # rtc 100 | List[float], # quantized_volume_offset and quantized_volume_scale 101 | Tuple[int, int, int, int], # constant_rgba 102 | ], 103 | ] 104 | 105 | 106 | class HierarchyClassDictType(TypedDict): 107 | name: str 108 | length: int 109 | instances: dict[str, list[Any]] 110 | 111 | 112 | # Tiler types 113 | 114 | PortionItemType = Tuple[int, ...] 115 | PortionsType = List[Tuple[str, PortionItemType]] 116 | 117 | 118 | class MetadataReaderType(TypedDict): 119 | portions: PortionsType 120 | aabb: npt.NDArray[np.float64] 121 | crs_in: CRS | None 122 | point_count: int 123 | avg_min: npt.NDArray[np.float64] 124 | 125 | 126 | OffsetScaleType = Tuple[ 127 | npt.NDArray[np.float64], 128 | npt.NDArray[np.float64], 129 | Optional[npt.NDArray[np.float64]], 130 | Optional[float], 131 | ] 132 | -------------------------------------------------------------------------------- /script/josm_complete_multipolygon_members.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # File: josm_complete_members.py 4 | 5 | import argparse, sys, time 6 | from typing import List, Dict, Tuple 7 | import xml.etree.ElementTree as ET 8 | import requests 9 | from urllib.parse import quote_plus 10 | 11 | JOSM = "http://127.0.0.1:8111" 12 | TIMEOUT = 30 13 | 14 | def rc_get(path: str): 15 | r = requests.get(f"{JOSM}{path}", timeout=TIMEOUT) 16 | r.raise_for_status() 17 | return r 18 | 19 | def josm_ping() -> dict: 20 | return rc_get("/version").json() 21 | 22 | def josm_complete_relation(rel_id: int, relation_members=True, referrers=True): 23 | """ 24 | Ask JOSM to add the relation (if missing) and download any missing 25 | members/referrers into the ACTIVE (current) data layer. 26 | """ 27 | url = f"/load_object?objects={quote_plus('r'+str(rel_id))}" 28 | if relation_members: url += "&relation_members=true" 29 | if referrers: url += "&referrers=true" 30 | rc_get(url) 31 | 32 | def parse_osm_file(path: str) -> Tuple[List[int], Dict[int, dict]]: 33 | """Return (relation_ids, tags_by_relation_id) from a local .osm/.osm.xml file.""" 34 | try: 35 | root = ET.parse(path).getroot() 36 | except ET.ParseError as e: 37 | raise RuntimeError(f"Invalid OSM XML in {path}: {e}") 38 | rel_ids, tag_map = [], {} 39 | for rel in root.findall("relation"): 40 | rid = int(rel.attrib["id"]) 41 | tags = {t.attrib["k"]: t.attrib["v"] for t in rel.findall("tag")} 42 | tag_map[rid] = tags 43 | rel_ids.append(rid) 44 | return rel_ids, tag_map 45 | 46 | def run(tag: str, osm_file: str, wait_after_each: float, dry_run: bool): 47 | if "=" not in tag: 48 | raise ValueError("tag must be 'key=value', e.g. natural=wood") 49 | key, value = tag.split("=", 1) 50 | 51 | print("JOSM RC:", josm_ping()) 52 | 53 | rel_ids, tag_map = parse_osm_file(osm_file) 54 | to_complete = [rid for rid in rel_ids 55 | if tag_map[rid].get("type") == "multipolygon" and tag_map[rid].get(key) == value] 56 | 57 | if not to_complete: 58 | print(f"No multipolygon relations with {tag} found in {osm_file}.") 59 | return 60 | 61 | print(f"Found {len(to_complete)} multipolygon relations with {tag}.") 62 | if dry_run: 63 | print("Dry-run: would request completion for relation IDs:") 64 | print(", ".join(map(str, to_complete))) 65 | return 66 | 67 | print("Requesting downloads into the CURRENT JOSM layer …") 68 | for rid in to_complete: 69 | josm_complete_relation(rid, relation_members=True, referrers=True) 70 | if wait_after_each > 0: 71 | time.sleep(wait_after_each) 72 | print("Done.") 73 | 74 | def main(): 75 | ap = argparse.ArgumentParser( 76 | description="Download & add incomplete members of tagged multipolygon relations into the CURRENT JOSM layer." 77 | ) 78 | ap.add_argument("--tag", required=True, help="OSM tag 'key=value', e.g. natural=wood") 79 | ap.add_argument("--osm-file", required=True, help="Path to local .osm/.osm.xml saved from JOSM") 80 | ap.add_argument("--wait-after-each", type=float, default=0.0, help="Seconds to wait after each download request") 81 | ap.add_argument("--dry-run", action="store_true", help="Print relation IDs and exit") 82 | args = ap.parse_args() 83 | try: 84 | run(args.tag, args.osm_file, args.wait_after_each, args.dry_run) 85 | except Exception as e: 86 | print(f"ERROR: {e}", file=sys.stderr); sys.exit(1) 87 | 88 | if __name__ == "__main__": 89 | main() 90 | -------------------------------------------------------------------------------- /pml/examples/house.pml: -------------------------------------------------------------------------------- 1 | @name "single family house"; 2 | 3 | @meta{ 4 | buildingUse: single_family; 5 | buildingLaf: modern; 6 | height: "low rise"; 7 | } 8 | 9 | footprint{ 10 | roofShape: gabled; 11 | numLevels: 1; 12 | numRoofLevels: 1; 13 | random: [ 14 | fooprint{ 15 | use: house_type_1; 16 | f1CladdingMaterial: brick; 17 | f1CladdingColor: gray; 18 | f0CladdingMaterial: brick; 19 | f0CladdingColor: gray; 20 | } 21 | fooprint{ 22 | use: house_type_2; 23 | f1CladdingMaterial: brick; 24 | f1CladdingColor: pink; 25 | f0CladdingMaterial: brick; 26 | f0CladdingColor: pink; 27 | } 28 | fooprint{ 29 | use: house_type_1; 30 | f1CladdingMaterial: vinyl; 31 | f1CladdingColor: khaki; 32 | f0CladdingMaterial: brick; 33 | f0CladdingColor: brown; 34 | } 35 | fooprint{ 36 | use: house_type_2; 37 | f1CladdingMaterial: vinyl; 38 | f1CladdingColor: lightblue; 39 | f0CladdingMaterial: brick; 40 | f0CladdingColor: darkgold; 41 | } 42 | ] 43 | } 44 | 45 | footprint@house_type_1{ 46 | markup: [ 47 | facade[item.front] { 48 | use: facade_front; 49 | } 50 | facade[item.back] { 51 | use: facade_back; 52 | } 53 | facade{ 54 | alternate: [ 55 | facade { 56 | use: facade_side_1; 57 | } 58 | facade { 59 | use: facade_side_2; 60 | } 61 | ] 62 | } 63 | ] 64 | } 65 | 66 | footprint@house_type_2{ 67 | markup: [ 68 | facade[item.front] { 69 | use: facade_front; 70 | } 71 | facade[item.back] { 72 | use: facade_back; 73 | } 74 | facade{ 75 | alternate: [ 76 | facade { 77 | use: facade_side_1; 78 | } 79 | facade { 80 | // empty facade 81 | } 82 | ] 83 | } 84 | ] 85 | } 86 | 87 | level@ground_floor { 88 | indices: (0,0); 89 | claddingMaterial: item.footprint["f0CladdingMaterial"]; 90 | claddingColor: item.footprint["f0CladdingColor"]; 91 | } 92 | 93 | level@ground_floor { 94 | indices: (1,1); 95 | claddingMaterial: item.footprint["f0CladdingMaterial"]; 96 | claddingColor: item.footprint["f0CladdingColor"]; 97 | } 98 | 99 | facade@facade_front{ 100 | class: facade_front; 101 | markup: [ 102 | level{ 103 | use: floor_1; 104 | markup: [ 105 | window{} 106 | ] 107 | } 108 | level{ 109 | use: ground_floor; 110 | markup: [ 111 | window{} door{} window{} 112 | ] 113 | } 114 | ] 115 | } 116 | 117 | facade@facade_back{ 118 | class: facade_back; 119 | markup: [ 120 | level{ 121 | use: floor_1; 122 | } 123 | level{ 124 | use: ground_floor; 125 | markup: [ 126 | window{} window{} window{} 127 | ] 128 | } 129 | ] 130 | } 131 | 132 | facade@facade_side_1{ 133 | class: facade_side_1; 134 | markup: [ 135 | level{ 136 | use: floor_1; 137 | markup: [ 138 | window{} window{} window{} 139 | ] 140 | } 141 | level{ 142 | use: ground_floor; 143 | markup: [ 144 | window{} window{} window{} 145 | ] 146 | } 147 | ] 148 | } 149 | 150 | facade@facade_side_2{ 151 | class: facade_side_2; 152 | markup: [ 153 | level{ 154 | use: floor_1; 155 | markup: [ 156 | window{} balcony{} window{} 157 | ] 158 | } 159 | level{ 160 | use: ground_floor; 161 | markup: [ 162 | window{} window{} 163 | ] 164 | } 165 | ] 166 | } 167 | -------------------------------------------------------------------------------- /pml/README.md: -------------------------------------------------------------------------------- 1 | # PML2PythonTranslator (Development Step I) 2 | The goal of the PML2PythonTranslator is to translate PML-code (PML stands for Prochitecture Markup Language) into Python code that describes the style of a building and is used in the [blender-osm](https://github.com/vvoovv/blender-osm) plugin for Blender. A description of the concept of this style can be found at [here](https://github.com/vvoovv/blender-osm/wiki/Concept-2.0). An additional goal of this development step was to study, how classes could made reusable for the node-based visual editor, which can be found in the repository [blosm-nodes](https://github.com/vvoovv/blosm-nodes/), and for other side projects of the blender-osm plugin. Only a subset of PML has been realized in this development step in order to get a feeling of the problems of this concept and to enable a discussion. 3 | ### Content 4 | ***pml_grammar (folder):*** 5 | This folder contains the file *pml.g4*, which defines the grammar of PML, written for the [ANTLR](https://www.antlr.org/) parser generator. All the other files in this folder have been generated by the ANTLR4 parser generator (see below) for the target language Python. Do not change the files in this folder. 6 | 7 | ***example.pml:*** 8 | This file contains an example description of a style, written in PML, as far as the translator already has been developed. 9 | 10 | ***PML2PythonTranslator.py:*** 11 | This file contains a demo main program that starts the translation process. The parser created by ANTLR4 creates a parse tree from any file containing PML style code and the walker class then calls the translating functions using the generated listener class. 12 | 13 | ***PythonListener.py:*** 14 | This file contains an interface class between the ANTLR4 listnener *pmlListener* and the coder class *PythonCoder*. It just calls the parser rule functions with the same name in the coder class, transferring the retrieved content from the parse tree. In principle, all the code in *PythonCoder* could be written directly in *PythonListener*, but as *PythonListener* depends on the ANTLR4 classes, it could not be reused in other programs without the ANTLR4 runtime for Python. *PythonListener* provides a method *getCode()* that gets the result of the translation for the *PythonCoder*. 15 | 16 | ***PythonCoder.py:*** 17 | This class provides all rule functions that have to create Python code for the *blender-osm* plugin. Actually, it provides a *write()* method, that writes the translated text into a string, accessible by the *getCode()* method of the class. The *write()* method could be overwritten by a derived class to produce other kind of output. As for instance parsing the nodes in the node-based visual editor is very similar to parsing PML style code, *PythonCoder.py* can be reused by this editor to create the same code. 18 | 19 | ***Dictionaries.py:*** 20 | This file contains only a fragement of code. It holds the data types and ranges of OSM attributes required by the osm-plugin. It could later be used to hold the information about allowed attribute names of the style blocks, to check if they are allowed in the context of the actual element, and so on. This will be object of future discussions. 21 | ### Prerequisites 22 | To run the PML2PythonTranslator, a Python 3 interpreter is required. 23 | 24 | If you like to change the grammar, ANTLR4 has to be installed (see [here](https://github.com/antlr/antlr4/blob/master/doc/getting-started.md)). The parser files for the Python 3 runtime can then be generated using 25 | ``` 26 | java org.antlr.v4.Tool -Dlanguage=Python3 pml.g4 27 | ``` 28 | ### Running the translator 29 | To run the translator using the example style *example.pml*, execute 30 | ``` 31 | python PML2PythonTranslator.py example.pml '' 32 | ``` 33 | The translated code will be written to *stdout*. 34 | --------------------------------------------------------------------------------