base for new renderer
This commit is contained in:
parent
d644ac614e
commit
97ffea6166
6 changed files with 152 additions and 277 deletions
|
@ -69,6 +69,12 @@ class SectionViewSet(MapdataViewSet):
|
|||
results.append(door)
|
||||
return Response([obj.to_geojson() for obj in results])
|
||||
|
||||
@detail_route(methods=['get'])
|
||||
def svg(self, requests, pk=None):
|
||||
section = self.get_object()
|
||||
response = HttpResponse(section.render_svg(), 'image/svg+xml')
|
||||
return response
|
||||
|
||||
|
||||
class BuildingViewSet(MapdataViewSet):
|
||||
""" Add ?geometry=1 to get geometries, add ?section=<id> to filter by section. """
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from shapely.geometry import CAP_STYLE, JOIN_STYLE
|
||||
from shapely.ops import cascaded_union
|
||||
|
||||
from c3nav.mapdata.models.base import EditorFormMixin
|
||||
from c3nav.mapdata.models.locations import SpecificLocation
|
||||
from c3nav.mapdata.utils.geometry import assert_multilinestring, assert_multipolygon
|
||||
from c3nav.mapdata.render.svg import SVGImage
|
||||
from c3nav.mapdata.utils.misc import get_dimensions
|
||||
|
||||
|
||||
class Section(SpecificLocation, EditorFormMixin, models.Model):
|
||||
|
@ -45,173 +46,48 @@ class Section(SpecificLocation, EditorFormMixin, models.Model):
|
|||
result['altitude'] = float(str(self.altitude))
|
||||
return result
|
||||
|
||||
def render_svg(self):
|
||||
width, height = get_dimensions()
|
||||
svg = SVGImage(width=width, height=height, scale=settings.RENDER_SCALE)
|
||||
|
||||
class SectionGeometries():
|
||||
by_section_id = {}
|
||||
building_geometries = cascaded_union(tuple(b.geometry for b in self.buildings.all()))
|
||||
|
||||
@classmethod
|
||||
def by_section(cls, section, only_public=True):
|
||||
return cls.by_section_id.setdefault((section.id, only_public), cls(section, only_public=only_public))
|
||||
spaces = self.spaces.all()
|
||||
space_levels = {
|
||||
'upper': [],
|
||||
'lower': [],
|
||||
'': [],
|
||||
}
|
||||
for space in spaces:
|
||||
space_levels[space.level].append(space)
|
||||
space_geometries = {
|
||||
level: cascaded_union(tuple((s.geometry.difference(building_geometries) if s.outside else s.geometry)
|
||||
for s in level_spaces))
|
||||
for level, level_spaces in space_levels.items()}
|
||||
|
||||
def __init__(self, section, only_public=True):
|
||||
self.section = section
|
||||
self.only_public = only_public
|
||||
hole_geometries = cascaded_union(tuple(h.geometry for h in self.holes.all()))
|
||||
hole_geometries = hole_geometries.intersection(space_geometries[''])
|
||||
hole_svg = svg.add_geometry(hole_geometries, 'holes')
|
||||
hole_mask = svg.add_mask(hole_svg, inverted=True, defid='holes-mask')
|
||||
|
||||
def query(self, name):
|
||||
queryset = getattr(self.section, name)
|
||||
if not self.only_public:
|
||||
return queryset.all()
|
||||
return queryset.filter(public=True)
|
||||
space_lower_svg = svg.add_geometry(space_geometries['lower'], defid='spaces-lower')
|
||||
svg.use_geometry(space_lower_svg, fill_color='#d1d1d1')
|
||||
|
||||
@cached_property
|
||||
def raw_rooms(self):
|
||||
return cascaded_union([room.geometry for room in self.query('rooms')])
|
||||
space_svg = svg.add_geometry(space_geometries[''], defid='spaces')
|
||||
space_hole_mask = svg.add_mask(space_svg, hole_svg, inverted=True, defid='spaces_mask')
|
||||
svg.use_geometry(space_svg, fill_color='#d1d1d1', mask=hole_mask)
|
||||
|
||||
@cached_property
|
||||
def buildings(self):
|
||||
result = cascaded_union([building.geometry for building in self.query('buildings')])
|
||||
if self.section.intermediate:
|
||||
result = cascaded_union([result, self.raw_rooms])
|
||||
return result
|
||||
building_svg = svg.add_geometry(building_geometries, 'buildings')
|
||||
svg.use_geometry(building_svg, fill_color='#929292', mask=space_hole_mask)
|
||||
|
||||
@cached_property
|
||||
def rooms(self):
|
||||
return self.raw_rooms.intersection(self.buildings)
|
||||
svg.use_geometry(space_svg, stroke_color='#333333', stroke_width=0.08)
|
||||
svg.use_geometry(building_svg, stroke_color='#333333', stroke_width=0.10)
|
||||
|
||||
@cached_property
|
||||
def outsides(self):
|
||||
return cascaded_union([outside.geometry for outside in self.query('outsides')]).difference(self.buildings)
|
||||
door_geometries = cascaded_union(tuple(d.geometry for d in self.doors.all()))
|
||||
door_geometries = door_geometries.difference(space_geometries[''])
|
||||
door_svg = svg.add_geometry(door_geometries, defid='doors')
|
||||
svg.use_geometry(door_svg, fill_color='#ffffff')
|
||||
|
||||
@cached_property
|
||||
def mapped(self):
|
||||
return cascaded_union([self.buildings, self.outsides])
|
||||
|
||||
@cached_property
|
||||
def lineobstacles(self):
|
||||
lineobstacles = []
|
||||
for obstacle in self.query('lineobstacles'):
|
||||
lineobstacles.append(obstacle.geometry.buffer(obstacle.width/2,
|
||||
join_style=JOIN_STYLE.mitre, cap_style=CAP_STYLE.flat))
|
||||
return cascaded_union(lineobstacles)
|
||||
|
||||
@cached_property
|
||||
def uncropped_obstacles(self):
|
||||
obstacles = [obstacle.geometry for obstacle in self.query('obstacles').filter(crop_to_level__isnull=True)]
|
||||
return cascaded_union(obstacles).intersection(self.mapped)
|
||||
|
||||
@cached_property
|
||||
def cropped_obstacles(self):
|
||||
levels_by_name = {}
|
||||
obstacles_by_crop_to_level = {}
|
||||
for obstacle in self.query('obstacles').filter(crop_to_level__isnull=False):
|
||||
level_name = obstacle.crop_to_level.name
|
||||
levels_by_name.setdefault(level_name, obstacle.crop_to_level)
|
||||
obstacles_by_crop_to_level.setdefault(level_name, []).append(obstacle.geometry)
|
||||
|
||||
all_obstacles = []
|
||||
for level_name, obstacles in obstacles_by_crop_to_level.items():
|
||||
obstacles = cascaded_union(obstacles).intersection(levels_by_name[level_name].geometries.mapped)
|
||||
all_obstacles.append(obstacles)
|
||||
all_obstacles.extend(assert_multipolygon(self.lineobstacles))
|
||||
|
||||
return cascaded_union(all_obstacles).intersection(self.mapped)
|
||||
|
||||
@cached_property
|
||||
def obstacles(self):
|
||||
return cascaded_union([self.uncropped_obstacles, self.cropped_obstacles])
|
||||
|
||||
@cached_property
|
||||
def raw_doors(self):
|
||||
return cascaded_union([door.geometry for door in self.query('doors').all()]).intersection(self.mapped)
|
||||
|
||||
@cached_property
|
||||
def raw_escalators(self):
|
||||
return cascaded_union([escalator.geometry for escalator in self.query('escalators').all()])
|
||||
|
||||
@cached_property
|
||||
def escalators(self):
|
||||
return self.raw_escalators.intersection(self.accessible)
|
||||
|
||||
@cached_property
|
||||
def elevatorlevels(self):
|
||||
return cascaded_union([elevatorlevel.geometry for elevatorlevel in self.query('elevatorlevels').all()])
|
||||
|
||||
@cached_property
|
||||
def areas(self):
|
||||
return cascaded_union([self.rooms, self.outsides, self.elevatorlevels])
|
||||
|
||||
@cached_property
|
||||
def holes(self):
|
||||
return cascaded_union([holes.geometry for holes in self.query('holes').all()]).intersection(self.areas)
|
||||
|
||||
@cached_property
|
||||
def accessible(self):
|
||||
return self.areas.difference(cascaded_union([self.holes, self.obstacles]))
|
||||
|
||||
@cached_property
|
||||
def buildings_with_holes(self):
|
||||
return self.buildings.difference(self.holes)
|
||||
|
||||
@cached_property
|
||||
def outsides_with_holes(self):
|
||||
return self.outsides.difference(self.holes)
|
||||
|
||||
@cached_property
|
||||
def areas_and_doors(self):
|
||||
return cascaded_union([self.areas, self.raw_doors])
|
||||
|
||||
@cached_property
|
||||
def walls(self):
|
||||
return self.buildings.difference(self.areas_and_doors)
|
||||
|
||||
@cached_property
|
||||
def walls_shadow(self):
|
||||
return self.walls.buffer(0.2, join_style=JOIN_STYLE.mitre).intersection(self.buildings_with_holes)
|
||||
|
||||
@cached_property
|
||||
def doors(self):
|
||||
return self.raw_doors.difference(self.areas)
|
||||
|
||||
def get_levelconnectors(self, to_level=None):
|
||||
queryset = self.query('levelconnectors').prefetch_related('levels')
|
||||
if to_level is not None:
|
||||
queryset = queryset.filter(levels=to_level)
|
||||
return cascaded_union([levelconnector.geometry for levelconnector in queryset])
|
||||
|
||||
@cached_property
|
||||
def levelconnectors(self):
|
||||
return cascaded_union([levelconnector.geometry for levelconnector in self.query('levelconnectors')])
|
||||
|
||||
@cached_property
|
||||
def intermediate_shadows(self):
|
||||
qs = self.query('levelconnectors').prefetch_related('levels').filter(levels__altitude__lt=self.section.altitude)
|
||||
connectors = cascaded_union([levelconnector.geometry for levelconnector in qs])
|
||||
shadows = self.buildings.difference(connectors.buffer(0.4, join_style=JOIN_STYLE.mitre))
|
||||
shadows = shadows.buffer(0.3)
|
||||
return shadows
|
||||
|
||||
@cached_property
|
||||
def hole_shadows(self):
|
||||
holes = self.holes.buffer(0.1, join_style=JOIN_STYLE.mitre)
|
||||
shadows = holes.difference(self.holes.buffer(-0.3, join_style=JOIN_STYLE.mitre))
|
||||
|
||||
qs = self.query('levelconnectors').prefetch_related('levels').filter(levels__altitude__lt=self.section.altitude)
|
||||
connectors = cascaded_union([levelconnector.geometry for levelconnector in qs])
|
||||
|
||||
shadows = shadows.difference(connectors.buffer(1.0, join_style=JOIN_STYLE.mitre))
|
||||
return shadows
|
||||
|
||||
@cached_property
|
||||
def stairs(self):
|
||||
return cascaded_union([stair.geometry for stair in self.query('stairs')]).intersection(self.accessible)
|
||||
|
||||
@cached_property
|
||||
def stair_areas(self):
|
||||
left = []
|
||||
for stair in assert_multilinestring(self.stairs):
|
||||
left.append(stair.parallel_offset(0.15, 'right', join_style=JOIN_STYLE.mitre))
|
||||
return cascaded_union(left).buffer(0.20, join_style=JOIN_STYLE.mitre, cap_style=CAP_STYLE.flat)
|
||||
|
||||
@cached_property
|
||||
def stuffedareas(self):
|
||||
return cascaded_union([stuffedarea.geometry for stuffedarea in self.query('stuffedareas')])
|
||||
space_upper_svg = svg.add_geometry(space_geometries['upper'], defid='spaces-upper')
|
||||
svg.use_geometry(space_upper_svg, fill_color='#d1d1d1')
|
||||
return svg.get_xml()
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
from c3nav.mapdata.models.section import Section
|
||||
from c3nav.mapdata.render.renderer import LevelRenderer # noqa
|
||||
|
||||
|
||||
def render_all_levels(show_accessibles=False):
|
||||
from c3nav.mapdata.models.section import Section
|
||||
from c3nav.mapdata.render.renderer import LevelRenderer # noqa
|
||||
|
||||
renderers = []
|
||||
for level in Section.objects.all():
|
||||
|
|
|
@ -2,14 +2,13 @@ import os
|
|||
import subprocess
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from django.conf import settings
|
||||
from shapely.affinity import scale
|
||||
from shapely.geometry import JOIN_STYLE, box
|
||||
|
||||
from c3nav.mapdata.inclusion import get_maybe_invisible_areas
|
||||
from c3nav.mapdata.utils.misc import get_dimensions, get_public_private_area, get_render_dimensions, get_render_path
|
||||
|
||||
|
||||
|
||||
class LevelRenderer():
|
||||
def __init__(self, level, only_public):
|
||||
self.level = level
|
||||
|
@ -23,41 +22,6 @@ class LevelRenderer():
|
|||
def get_filename(self, mode, filetype, level=None):
|
||||
return get_render_path(filetype, self.level.name if level is None else level, mode, self.only_public)
|
||||
|
||||
@staticmethod
|
||||
def polygon_svg(geometry, fill_color=None, fill_opacity=None,
|
||||
stroke_width=0.0, stroke_color=None, stroke_opacity=None):
|
||||
scaled = scale(geometry, xfact=settings.RENDER_SCALE, yfact=settings.RENDER_SCALE, origin=(0, 0))
|
||||
element = ET.fromstring(scaled.svg(0, fill_color or '#FFFFFF'))
|
||||
if element.tag != 'g':
|
||||
new_element = ET.Element('g')
|
||||
new_element.append(element)
|
||||
element = new_element
|
||||
|
||||
paths = element.findall('polyline')
|
||||
if len(paths) == 0:
|
||||
paths = element.findall('path')
|
||||
|
||||
for path in paths:
|
||||
path.attrib.pop('opacity')
|
||||
path.set('stroke-width', str(stroke_width * settings.RENDER_SCALE))
|
||||
|
||||
if fill_color is None and 'fill' in path.attrib:
|
||||
path.attrib.pop('fill')
|
||||
path.set('fill-opacity', '0')
|
||||
|
||||
if fill_opacity is not None:
|
||||
path.set('fill-opacity', str(fill_opacity))
|
||||
|
||||
if stroke_color is not None:
|
||||
path.set('stroke', stroke_color)
|
||||
elif 'stroke' in path.attrib:
|
||||
path.attrib.pop('stroke')
|
||||
|
||||
if stroke_opacity is not None:
|
||||
path.set('stroke-opacity', str(stroke_opacity))
|
||||
|
||||
return element
|
||||
|
||||
def create_svg(self):
|
||||
width, height = get_render_dimensions()
|
||||
svg = ET.Element('svg', {
|
||||
|
|
|
@ -12,12 +12,15 @@ class SVGGroup:
|
|||
self.height = height
|
||||
self.scale = scale
|
||||
self.g = ET.Element('g', {
|
||||
'transform': 'scale(1 -1) translate(0 -%d)' % (self.height),
|
||||
'transform': 'scale(1 -1) translate(0 -%d)' % (self.height*scale),
|
||||
})
|
||||
|
||||
def get_xml(self):
|
||||
def get_element(self):
|
||||
return self.g
|
||||
|
||||
def get_xml(self):
|
||||
return ET.tostring(self.get_element()).decode()
|
||||
|
||||
def add_group(self):
|
||||
group = SVGGroup(self.width, self.height, self.scale)
|
||||
self.g.append(group)
|
||||
|
@ -27,22 +30,36 @@ class SVGGroup:
|
|||
class SVGImage(SVGGroup):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.defs = ET.Element('defs')
|
||||
self.def_i = 0
|
||||
|
||||
def get_xml(self):
|
||||
# blur_filter = ET.Element('filter', {'id': 'wallblur'})
|
||||
# blur_filter.append(ET.Element('feGaussianBlur', {'in': 'SourceGraphic', 'stdDeviation': str(5*self.scale)}))
|
||||
# self.defs.append(blur_filter)
|
||||
|
||||
def get_element(self):
|
||||
root = ET.Element('svg', {
|
||||
'width': str(self.width),
|
||||
'height': str(self.height),
|
||||
'width': str(self.width*self.scale),
|
||||
'height': str(self.height*self.scale),
|
||||
'xmlns:svg': 'http://www.w3.org/2000/svg',
|
||||
'xmlns': 'http://www.w3.org/2000/svg',
|
||||
'xmlns:xlink': 'http://www.w3.org/1999/xlink',
|
||||
})
|
||||
root.append(self.defs)
|
||||
root.append(self.g)
|
||||
return root
|
||||
|
||||
def add_geometry(self, geometry, fill_color=None, fill_opacity=None,
|
||||
stroke_width=0.0, stroke_color=None, stroke_opacity=None):
|
||||
def new_defid(self):
|
||||
defid = 's'+str(self.def_i)
|
||||
self.def_i += 1
|
||||
return defid
|
||||
|
||||
def add_geometry(self, geometry, defid=None, comment=None):
|
||||
if defid is None:
|
||||
defid = self.new_defid()
|
||||
|
||||
scaled = scale(geometry, xfact=self.scale, yfact=self.scale, origin=(0, 0))
|
||||
element = ET.fromstring(scaled.svg(0, fill_color or '#FFFFFF'))
|
||||
element = ET.fromstring(scaled.svg(0, '#FFFFFF'))
|
||||
if element.tag != 'g':
|
||||
new_element = ET.Element('g')
|
||||
new_element.append(element)
|
||||
|
@ -53,74 +70,88 @@ class SVGImage(SVGGroup):
|
|||
paths = element.findall('path')
|
||||
|
||||
for path in paths:
|
||||
path.attrib.pop('opacity')
|
||||
path.set('stroke-width', str(stroke_width * self.scale))
|
||||
path.attrib.pop('opacity', None)
|
||||
path.attrib.pop('fill', None)
|
||||
path.attrib.pop('fill-rule', None)
|
||||
path.attrib.pop('stroke', None)
|
||||
path.attrib.pop('stroke-width', None)
|
||||
|
||||
if fill_color is None and 'fill' in path.attrib:
|
||||
path.attrib.pop('fill')
|
||||
path.set('fill-opacity', '0')
|
||||
element.set('id', defid)
|
||||
self.defs.append(element)
|
||||
return defid
|
||||
|
||||
if fill_opacity is not None:
|
||||
path.set('fill-opacity', str(fill_opacity))
|
||||
def add_mask(self, *geometries, inverted=False, defid=None):
|
||||
if defid is None:
|
||||
defid = self.new_defid()
|
||||
|
||||
if stroke_color is not None:
|
||||
path.set('stroke', stroke_color)
|
||||
elif 'stroke' in path.attrib:
|
||||
path.attrib.pop('stroke')
|
||||
mask = ET.Element('mask', {'id': defid})
|
||||
mask.append(ET.Element('rect', {'width': '100%', 'height': '100%', 'fill': 'white' if inverted else 'black'}))
|
||||
for geometry in geometries:
|
||||
mask.append(ET.Element('use', {'xlink:href': '#'+geometry, 'fill': 'black' if inverted else 'white'}))
|
||||
self.defs.append(mask)
|
||||
return defid
|
||||
|
||||
if stroke_opacity is not None:
|
||||
path.set('stroke-opacity', str(stroke_opacity))
|
||||
def add_union(self, *geometries, wall_shadow=False, defid=None):
|
||||
if defid is None:
|
||||
defid = self.new_defid()
|
||||
|
||||
element = ET.Element('g', {'id': defid})
|
||||
for geometry in geometries:
|
||||
newelem = ET.Element('use', {'xlink:href': '#'+geometry})
|
||||
if wall_shadow:
|
||||
newelem.set('filter', 'url(#wallshadow)')
|
||||
element.append(newelem)
|
||||
self.defs.append(element)
|
||||
return defid
|
||||
|
||||
def add_intersection(self, geometry1, geometry2, defid=None):
|
||||
if defid is None:
|
||||
defid = self.new_defid()
|
||||
|
||||
mask = ET.Element('mask', {'id': defid+'-mask'})
|
||||
mask.append(ET.Element('rect', {'width': '100%', 'height': '100%', 'fill': 'black'}))
|
||||
mask.append(ET.Element('use', {'xlink:href': '#'+geometry2, 'fill': 'white'}))
|
||||
self.defs.append(mask)
|
||||
|
||||
element = ET.Element('g', {'id': defid, 'mask': 'url(#'+defid+'-mask)'})
|
||||
element.append(ET.Element('use', {'xlink:href': '#'+geometry1}))
|
||||
self.defs.append(element)
|
||||
return defid
|
||||
|
||||
def add_difference(self, geometry1, geometry2, defid=None):
|
||||
if defid is None:
|
||||
defid = self.new_defid()
|
||||
|
||||
mask = ET.Element('mask', {'id': defid+'-mask'})
|
||||
mask.append(ET.Element('rect', {'width': '100%', 'height': '100%', 'fill': 'white'}))
|
||||
mask.append(ET.Element('use', {'xlink:href': '#'+geometry2, 'fill': 'black'}))
|
||||
self.defs.append(mask)
|
||||
|
||||
element = ET.Element('g', {'id': defid, 'mask': 'url(#'+defid+'-mask)'})
|
||||
element.append(ET.Element('use', {'xlink:href': '#' + geometry1}))
|
||||
self.defs.append(element)
|
||||
return defid
|
||||
|
||||
def use_geometry(self, geometry, fill_color=None, fill_opacity=None, opacity=None, mask=None, filter=None,
|
||||
stroke_width=0.0, stroke_color=None, stroke_opacity=None, stroke_linejoin=None):
|
||||
element = ET.Element('use', {'xlink:href': '#'+geometry})
|
||||
element.set('fill', fill_color or 'none')
|
||||
if fill_opacity:
|
||||
element.set('fill-opacity', str(fill_opacity))
|
||||
if stroke_width:
|
||||
element.set('stroke-width', str(stroke_width * self.scale))
|
||||
if stroke_color:
|
||||
element.set('stroke', stroke_color)
|
||||
if stroke_opacity:
|
||||
element.set('stroke-opacity', str(stroke_opacity))
|
||||
if stroke_linejoin:
|
||||
element.set('stroke-linejoin', str(stroke_linejoin))
|
||||
if opacity:
|
||||
element.set('opacity', str(opacity))
|
||||
if mask:
|
||||
element.set('mask', 'url(#'+mask+')')
|
||||
if filter:
|
||||
element.set('filter', 'url(#'+filter+')')
|
||||
|
||||
self.g.append(element)
|
||||
return element
|
||||
|
||||
|
||||
class MapRenderer(ABC):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def create_svg(self):
|
||||
width, height = get_render_dimensions()
|
||||
svg = ET.Element('svg', {
|
||||
'width': str(width),
|
||||
'height': str(height),
|
||||
'xmlns:svg': 'http://www.w3.org/2000/svg',
|
||||
'xmlns': 'http://www.w3.org/2000/svg',
|
||||
'xmlns:xlink': 'http://www.w3.org/1999/xlink',
|
||||
})
|
||||
return svg
|
||||
|
||||
@staticmethod
|
||||
def polygon_svg(geometry, fill_color=None, fill_opacity=None,
|
||||
stroke_width=0.0, stroke_color=None, stroke_opacity=None):
|
||||
scaled = scale(geometry, xfact=settings.RENDER_SCALE, yfact=settings.RENDER_SCALE, origin=(0, 0))
|
||||
element = ET.fromstring(scaled.svg(0, fill_color or '#FFFFFF'))
|
||||
if element.tag != 'g':
|
||||
new_element = ET.Element('g')
|
||||
new_element.append(element)
|
||||
element = new_element
|
||||
|
||||
paths = element.findall('polyline')
|
||||
if len(paths) == 0:
|
||||
paths = element.findall('path')
|
||||
|
||||
for path in paths:
|
||||
path.attrib.pop('opacity')
|
||||
path.set('stroke-width', str(stroke_width * settings.RENDER_SCALE))
|
||||
|
||||
if fill_color is None and 'fill' in path.attrib:
|
||||
path.attrib.pop('fill')
|
||||
path.set('fill-opacity', '0')
|
||||
|
||||
if fill_opacity is not None:
|
||||
path.set('fill-opacity', str(fill_opacity))
|
||||
|
||||
if stroke_color is not None:
|
||||
path.set('stroke', stroke_color)
|
||||
elif 'stroke' in path.attrib:
|
||||
path.attrib.pop('stroke')
|
||||
|
||||
if stroke_opacity is not None:
|
||||
path.set('stroke-opacity', str(stroke_opacity))
|
||||
|
||||
return element
|
||||
|
|
|
@ -50,7 +50,7 @@ else:
|
|||
debug_fallback = "runserver" in sys.argv
|
||||
DEBUG = config.getboolean('django', 'debug', fallback=debug_fallback)
|
||||
DIRECT_EDITING = config.getboolean('c3nav', 'direct_editing', fallback=DEBUG)
|
||||
RENDER_SCALE = float(config.get('c3nav', 'render_scale', fallback=12.5))
|
||||
RENDER_SCALE = float(config.get('c3nav', 'render_scale', fallback=20.0))
|
||||
|
||||
db_backend = config.get('database', 'backend', fallback='sqlite3')
|
||||
DATABASES = {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue