def field_lines_area(area, lines_method, padding=0.5, **kwargs): """ Playground fields are seen x: length, y: width """ rectangle = ddd.geomops.inscribed_rectangle(area, padding=padding) if not rectangle.geom or not rectangle.geom.exterior: return coords = rectangle.geom.exterior.coords width_seg = ddd.line([coords[0], coords[1]]) length_seg = ddd.line([coords[1], coords[2]]) width_l = width_seg.geom.length length_l = length_seg.geom.length if width_l > length_l: (width_l, length_l) = (length_l, width_l) (width_seg, length_seg) = (length_seg, width_seg) # Generate lines, rotate and translate to area lines = lines_method(length_l, width_l, **kwargs) angle = math.atan2( length_seg.geom.coords[1][1] - length_seg.geom.coords[0][1], length_seg.geom.coords[1][0] - length_seg.geom.coords[0][0]) lines = lines.rotate([0, 0, angle]).translate([ rectangle.geom.centroid.coords[0][0], rectangle.geom.centroid.coords[0][1], 0 ]) return lines
def generate_item_3d_historic_archaeological_site(self, item_2d): # TODO: Move the actual site generation, given an area, to sketchy coords = item_2d.centroid().geom.coords[0] if item_2d.geom.type in ("Point", "LineString"): points = item_2d.buffer(5.0).random_points(12) else: points = item_2d.random_points(12) line1 = ddd.line(points[0:2]) line2 = ddd.line(points[2:5]) line3 = ddd.line(points[5:]) geomobj = ddd.group2([line1, line2, line3]).buffer(0.5).union() #geomobj = filters.noise_random(geomobj, scale=0.075).clean().remove_z() #geomobj.show() item_3d = geomobj.extrude(0.8) #item_3d.copy_from(item_2d) item_3d = filters.noise_random(item_3d, scale=0.3) item_3d = item_3d.material(ddd.mats.tiles_stones_veg_sparse) item_3d = ddd.uv.map_cubic(item_3d) # Dont translate ir rotate, item is already in world space (built from item_2d). #item_3d = item_3d.rotate([0, 0, item_2d.get('ddd:angle', 0) - math.pi / 2]) #item_3d = item_3d.translate([coords[0], coords[1], 0.0]) # mat_bronze item_3d.name = 'Archaeological Site: %s' % item_2d.name return item_3d
def pipeline_logo(pipeline, root): """ """ logo = ddd.group3() thick = 0.125 margin = 0.4 line_out = ddd.line([ (1.0, 0.0, 0.4), (1.0, 0.0, 0.0), (0.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 1.0, 1.0), (1.0, 1.0, 1.0), (1.0, 0.0, 1.0), (1.0, 0.0, 0.6), ]) base = ddd.rect([thick, thick], name="Logo exterior").recenter() item = base.extrude_along(line_out) item = item.material(ddd.mats.steel) #item = item.rotate([0, 0, 0.2]) item = ddd.uv.map_cubic( item ) # FIXME: One of the corner vertices are not being split (but they are if slightly rotated) logo.append(item) line_in = ddd.line([ (1.0 - margin, 1.0 - margin - 0.1, 1), (1.0 - margin, 1.0 - margin, 1), (0.0, 1.0 - margin, 1), (0.0, 0.0, 1), (1.0 - margin, 0.0, 1), (1.0 - margin, 0.0, margin), (0.0, 0.0, margin), (0.0, 1.0 - margin, margin), (0.0, 1.0 - margin, 1.0 - margin), (0.0, 1.0 - margin - 0.1, 1.0 - margin), ]) item = base.extrude_along(line_in) item = item.material(ddd.mats.green) item = ddd.uv.map_cubic(item) logo.append(item) #logo = logo.scale([2, 2, 2]) logo.show() root.append(logo) root.append(ddd.helper.all(center=[10, 10, 1]))
def religion_cross(width=1, height=1.5): """ Religion cross. """ l1 = ddd.line([[-width / 2, height - width], [width / 2, height - width]]).buffer(width / 8.0, cap_style=2) l2 = ddd.line([[0, 0], [0, height]]).buffer(width / 8.0, cap_style=2) sign = l1.union(l2) sign = sign.extrude(width / 4.0) sign = sign.translate([0, 0, -width / 8.0]).rotate([-math.pi / 2.0, 0, 0]) sign = sign.translate([0, 0, height]) sign = ddd.uv.map_cubic(sign) sign.name = "Cross" return sign
def sign_pharmacy(size=1.0, depth=0.3): ''' A pharmacy sign (cross). Sits centered on its back (vertical plane). ''' l1 = ddd.line([[-size / 2, 0], [size / 2, 0]]).buffer(size / 3.0, cap_style=3) l2 = ddd.line([[0, -size / 2], [0, size / 2]]).buffer(size / 3.0, cap_style=3) sign = l1.union(l2) sign = sign.extrude(depth) sign = sign.rotate([math.pi / 2.0, 0, 0]) sign = sign.material(ddd.material(color='#00ff00')) sign = ddd.uv.map_cubic(sign) sign.name = "Pharmacy Sign" return sign
def field_lines_area(area, lines_method, padding=0.5, search_erode=0.5, **kwargs): """ Playground fields are seen x: length, y: width """ rectangle = ddd.geomops.inscribed_rectangle(area, padding=padding, search_erode=search_erode) if not rectangle.geom or not rectangle.geom.exterior: return coords = rectangle.geom.exterior.coords width_seg = ddd.line([coords[0], coords[1]]) length_seg = ddd.line([coords[1], coords[2]]) width_l = width_seg.geom.length length_l = length_seg.geom.length if width_l > length_l: (width_l, length_l) = (length_l, width_l) (width_seg, length_seg) = (length_seg, width_seg) # Generate lines, rotate and translate to area lines = lines_method(length_l, width_l, **kwargs) angle = math.atan2( length_seg.geom.coords[1][1] - length_seg.geom.coords[0][1], length_seg.geom.coords[1][0] - length_seg.geom.coords[0][0]) lines = lines.rotate([0, 0, angle]).translate([ rectangle.geom.centroid.coords[0][0], rectangle.geom.centroid.coords[0][1], 0 ]) field_lines_only = lines.select_remove( func=lambda o: o.mat == ddd.mats.painted_line) field_lines_only = field_lines_only.combine() field_lines_only = ddd.uv.map_cubic(field_lines_only) field_lines_only.name = "Combined field lines: %s" % lines.name field = lines.append(field_lines_only) return field
def to_geom(self, resolution): """ Currently, resolution is interpolated on the x axis. Returns a linestring. """ length = Vector2.distance(self.start, self.end) numpoints = length / resolution coords = [] for x in numpy.linspace(self.start[0], self.end[0], numpoints, endpoint=True): coords.append((x, self.curve.evaluate_y(x))) obj = ddd.line(coords) obj.copy_from(self) return obj
def pipeline_test_line_substring(pipeline, root, logger): """ Tests geometric operations. """ items = ddd.group3() # Test substrings obj = ddd.line([(0, 0), (4, 0)]) obj2 = obj.line_substring(1.0, -1.0) result = ddd.group([ obj.buffer(0.1, cap_style=ddd.CAP_FLAT), obj2.buffer(0.1, cap_style=ddd.CAP_FLAT).material(ddd.MAT_HIGHLIGHT) ]) result.show()
def catenary_cable(a, b, thick=0.10, length_ratio=1.1): """ TODO: move catenary calculations to "path" module and also check Trimesh paths to support that. The library requires the anchor to be below the fairlead. """ a = np.array(a) b = np.array(b) dist = np.linalg.norm(a - b) if (a[2] > b[2]): a, b = b, a length = dist * length_ratio w = 0 # submerged weight EA = 560e3 # axial stiffness floor = False # if True, contact is possible at the level of the ancho l1 = MooringLine(L=length, w=w, EA=EA, anchor=a, fairlead=b, floor=floor) l1.computeSolution() #l1.plot2D() #l1.plot3D() # get xyz coordinates along line (between 0. and total line length) #T = l1.getTension(s) #xyz = l1.s2xyz(s) #dxyz = l1.ds2xyz(s) # Build cable points_d = np.linspace(0, length, int(length / 3) + 3, endpoint=True) points_cable = [] for d in points_d: p = l1.s2xyz(d) points_cable.append(p) base = ddd.point(name="Catenary").buffer(thick / 2, resolution=3) path = ddd.line(points_cable) item = base.extrude_along(path) #item = item.rotate(ddd.ROT_FLOOR_TO_FRONT).rotate(ddd.ROT_TOP_CW) item = item.material(ddd.mats.steel) item = item.merge_vertices() item = item.smooth(angle=math.pi * 2/3) item = ddd.uv.map_cubic(item, split=False) # FIXME: Incorrect, should map along catenary, during construction return item
def process_node(prefix, node): node_path = prefix + "/" + node.name if (node.type == "Polygon2D"): #print(node['polygon']) #print(node['polygon'].__class__.__name__) coords = godot_vector2array(node['polygon'].args) position = godot_vector2(node['position'].args) if ( 'position' in node.properties) else [0, 0] #scale = godot_vector2(node['scale'].args) if ('scale' in node.properties) else [1, 1] rotation = node['rotation'] if ('rotation' in node.properties) else 0.0 visible = node['visible'] if ('visible' in node.properties) else True feat = ddd.polygon(coords, name=node.name) feat = feat.rotate(rotation) feat = feat.translate(position) # Transform should be maintained #feat = feat.scale([0.6, 0.6]) #T TODO: support scale somehow (ideally through godot hierarchy, but at least in metadata) feat.extra['godot:node:path'] = node_path feat.extra['godot:visible'] = visible #print(node_path) process_node_meta(feat, node) features.append(feat) elif (node.type == "Line2D"): coords = godot_vector2array(node['points'].args) position = godot_vector2(node['position'].args) if ( 'position' in node.properties) else [0, 0] visible = node['visible'] if ('visible' in node.properties) else True feat = ddd.line(coords, name=node.name) feat = feat.translate(position) feat.extra['godot:node:path'] = node_path feat.extra['godot:visible'] = visible process_node_meta(feat, node) features.append(feat) for c in node.get_children(): process_node(prefix + "/" + node.name, c)
def ladder_pool(height=1.75, width=0.6): """ """ arc_thick = 0.08 bar_thick = 0.05 arc_radius = 0.35 steps_interval = 0.30 height_above_ground = arc_radius circleline = ddd.point([-arc_radius, 0], "Ladder").arc_to([arc_radius, 0], [0, 0], False, resolution=4) circleline = circleline.line_rel([0, -(height - height_above_ground)]) circleline = circleline.arc_to([0, -(height - height_above_ground + arc_radius)], [0, -(height - height_above_ground)], False) #circleline = circleline.simplify(0.01) #regularpolygon(sides * 2, name="Childrens Playground Arc Side Arc").rotate(-math.pi / 2).outline().scale([length / 2, height]) #arcline = circleline.intersection(ddd.rect([-length, 0.1, length, height * 2])) arc = circleline.buffer(arc_thick / 2, cap_style=ddd.CAP_FLAT) #.intersection(ddd.rect([-length, 0, length, height * 2])) arc = arc.extrude(arc_thick, center=True).material(ddd.mats.steel) arc = arc.rotate(ddd.ROT_FLOOR_TO_FRONT) arc = ddd.uv.map_cubic(arc) arc1 = arc.copy().translate([0, -width / 2, 0]) arc2 = arc.copy().translate([0, +width / 2, 0]) item = ddd.group([arc1, arc2]) bar = ddd.point(name="Ladder step").buffer(bar_thick / 2).extrude(width - arc_thick, center=True).rotate(ddd.ROT_FLOOR_TO_FRONT) bar = bar.material(ddd.mats.steel) bar = ddd.uv.map_cubic(bar) stepsline = ddd.line([[arc_radius, 0], [arc_radius, -(height - height_above_ground)]]) numsteps = int(stepsline.length() / steps_interval) + 1 step_interval_adjusted = stepsline.length() / numsteps for idx in range(numsteps): (p, segment_idx, segment_coords_a, segment_coords_b) = stepsline.interpolate_segment(idx * step_interval_adjusted) pbar = bar.copy().translate([p[0], 0, p[1]]) item.append(pbar) item = item.combine() item = item.rotate(ddd.ROT_TOP_CW) return item
def solids_borders(root, pipeline, obj): if 'godot:material' in obj.extra: del(obj.extra['godot:material']) floors = ddd.group2(name="Floors") ceilings = ddd.group2(name="Ceilings") walls = ddd.group2(name="Walls") angles_ceiling = [-math.pi / 4, math.pi / 4] angles_floor1 = [math.pi / 4 * 3, math.pi / 4 * 4] angles_floor2 = [-math.pi / 4 * 4, -math.pi / 4 * 3] polygon = obj.geom.exterior if polygon.is_ccw: polygon.coords = reversed(list(polygon.coords)) segments = zip(polygon.coords, polygon.coords[1:] + polygon.coords[:1]) for a, b in segments: angle = math.atan2(b[1] - a[1], b[0] - a[0]) if (angle >= angles_floor1[0] and angle <= angles_floor1[1]): borderline = ddd.line([a, b]) borderline.extra.update(obj.extra) floors.append(borderline) if (angle >= angles_floor2[0] and angle <= angles_floor2[1]): borderline = ddd.line([a, b]) borderline.extra.update(obj.extra) floors.append(borderline) if (angle >= angles_ceiling[0] and angle <= angles_ceiling[1]): borderline = ddd.line([a, b]) borderline.extra.update(obj.extra) ceilings.append(borderline) ''' ddd.mats.grass = ddd.material(name="Grass", color='#2dd355', texture_path="res://assets/scene/props/grass-texture-tiled.png", #alpha_cutoff=0.05, #extra={'ddd:collider': False, 'ddd:shadows': False, 'uv:scale': 0.05} ) ''' floors2 = ddd.group2(name="Floors Background") floor_lines = floors floors = ddd.line([[0, 0], [1, 1]], name="Floors") floors.extra.update(obj.extra) floors.geom = linemerge([g.geom for g in floor_lines.children]) # Iterate merged lines floors = floors.individualize(always=True).clean() for line in floors.children: floor = line.buffer(8.0, resolution=1, cap_style=ddd.CAP_FLAT) # cap_style=ddd.CAP_FLAT) floor = floor.material(ddd.mats.grass_fore) floor.name = "Floor Fore: %s" % line.name floor.extra['floor_line'] = line.copy() #floor.extra['godot:material'] = "grass_fore" floor.extra['ddd:z_index'] = 46 # floor = floor.subtract(obj) floor = uvmapping.map_2d_path(floor, line, line_x_offset=64.0, line_x_width=63.0) if 'uv' in floor.extra: floor.extra['uv'] = [(v[0], v[1] * 4.0) for v in floor.extra['uv']] # temp: transposed and scaled floor.extra['ddd:collider'] = False floor.extra['ddd:occluder'] = False floor.extra['solid'] = False floor.extra['godot:light_mask'] = 0 # floor = filters.noise_random(floor, scale=3.0) # ddd.trace(locals()) # print(floor.get('uv', None)) floor2 = line.buffer(18.0, resolution=3, cap_style=ddd.CAP_FLAT) floor2 = floor2.material(ddd.mats.grass) floor2.name = "Floor: %s" % line.name floor2.extra['floor_line'] = line.copy() #floor.extra['godot:material'] = "grass" floor2.extra['ddd:z_index'] = -1 floor2 = uvmapping.map_2d_path(floor2, line, line_x_offset=64.0, line_x_width=63.0) if 'uv' in floor2.extra: floor2.extra['uv'] = [(v[0], 16.0 + v[1] * 3.5)for v in floor2.extra['uv']] # temp: transposed and scaled # floor2 = filters.noise_random(floor2, scale=3.0) floor2.extra['ddd:collider'] = False floor2.extra['ddd:occluder'] = False floor2.extra['solid'] = False floor2.extra['godot:light_mask'] = 1 min_length = 50 if line.length() > min_length and not obj.get('border:floor', None) == 'false': floors2.append(floor2) line.replace(floor) else: line.geom = None # for f2 in floors2: floors.append(f2) floors.append(floors2) # floors.extra['ddd:z_index'] = 40 # newobj = ddd.group2([obj, floors], name="Solid") # obj.replace(newobj) root.find("/Rooms").append(floors) obj.extra['floors'] = floors ceiling_lines = ceilings lines = linemerge([g.geom for g in ceiling_lines.children]) ceilings = DDDObject2(name="Ceilings", geom=lines) ceilings.extra.update(obj.extra) ceilings.extra['ddd:z_index'] = 40 ceilings = ceilings.individualize(always=True).clean() for pc in ceilings.children: c = pc.copy() c.extra['ceiling_line'] = c.copy() c = c.buffer(15.0) c = filters.noise_random(c, scale=10.0) pc.replace(c) ceilings = ceilings.material(ddd.mats.bricks) # ceilings.mat.color_rgba[3] = 128 ceilings = ceilings.clean() ceilings.set('ddd:collider', False, children=True) ceilings.set('ddd:occluder', False, children=True) ceilings.set('solid', False, children=True) ceilings.set('godot:light_mask', 1, children=True) # newobj = ddd.group2([obj, floors], name="Solid") # obj.replace(newobj) root.find("/Rooms").append(ceilings) obj.extra['ceilings'] = ceilings
from ddd.text import fonts items = ddd.group3() # Extrusion with optional caps fig = ddd.disc().extrude(5) items.append(fig) fig = ddd.disc().extrude(5, base=False) items.append(fig) fig = ddd.disc().extrude(5, cap=False) items.append(fig) fig = ddd.disc().extrude(5, cap=False, base=False) items.append(fig) # Extrude line (to faces, not volume) fig1 = ddd.line([[-2, 0], [0, 0], [2, 2]]) fig = fig1.extrude(2.0) items.append(fig) # Extrusion to line (explicit) fig1 = ddd.rect([-4, -2, 4, 2]) fig2 = ddd.line([[-4, 0], [4, 0]]) fig = fig1.extrude_step(fig2, 1.0) items.append(fig) # Extrusion to line (explicit) fig1 = ddd.rect([-4, -2, 4, 2]) fig2 = ddd.line([[-3, 0], [3, 0]]) fig = fig1.extrude_step(fig2, 1.0) items.append(fig)
def basketball_field_lines(length=28, width=15, line_width=0.075): """ Note that an additional 2m around the field shall be granted. Playground fields are seen x: length, y: width """ item = ddd.group3(name="Basketball lines") length = min(length, 28) width = min(width, 15) (length, width) = enforce_aspect_ratio(length, width, 28 / 15) rectangle = ddd.rect([-length / 2, -width / 2, length / 2, width / 2]) coords = rectangle.geom.exterior.coords width_seg = ddd.line([coords[0], coords[1]]) length_seg = ddd.line([coords[1], coords[2]]) width2_seg = ddd.line([coords[2], coords[3]]) length2_seg = ddd.line([coords[3], coords[0]]) width_l = width_seg.geom.length length_l = length_seg.geom.length exterior = rectangle.outline().buffer( line_width, cap_style=ddd.CAP_SQUARE).triangulate().material(ddd.mats.painted_line) exterior.name = "Bounds line" exterior.extra['ddd:collider'] = False exterior.extra['ddd:shadows'] = False midline_2d = ddd.line( [length_seg.geom.centroid, length2_seg.geom.centroid], name="Mid line") midline = midline_2d.buffer( line_width, cap_style=ddd.CAP_SQUARE).triangulate().material(ddd.mats.painted_line) midline.extra['ddd:collider'] = False midline.extra['ddd:shadows'] = False midcircle_radius_ratio = (3.60 / 2) / 15 midcircle = ddd.disc(center=midline_2d.geom.centroid.coords, r=width_l * midcircle_radius_ratio).outline() midcircle = midcircle.buffer( line_width, cap_style=ddd.CAP_SQUARE).triangulate().material(ddd.mats.painted_line) midcircle.extra['ddd:collider'] = False midcircle.extra['ddd:shadows'] = False item.append(exterior) item.append(midline) item.append(midcircle) centralline_2d = ddd.line( [width_seg.geom.centroid, width2_seg.geom.centroid], name="Central line") # Sides for side in (-1, 1): if width > 12.0: smallarea = ddd.line([[0, -3], [5.80, -(3 - 1.80)] ]).arc_to([5.80, 3 - 1.80], center=[5.80, 0], ccw=True).line_to([0, 3]) #smallarea = smallarea.scale([smallarea_length_ratio * length, smallarea_width_ratio * width * 0.5]) if side == 1: smallarea = smallarea.rotate(math.pi) smallarea = smallarea.translate([side * length_l / 2, 0]) smallarea = smallarea.buffer( line_width, cap_style=ddd.CAP_SQUARE).triangulate().material( ddd.mats.painted_line) smallarea.extra['ddd:collider'] = False smallarea.extra['ddd:shadows'] = False item.append(smallarea) smallline = ddd.line([[5.80, -(3 - 1.80)], [5.80, 3 - 1.80]]) if side == 1: smallline = smallline.rotate(math.pi) smallline = smallline.translate([side * length_l / 2, 0]) smallline = smallline.buffer( line_width, cap_style=ddd.CAP_SQUARE).triangulate().material( ddd.mats.painted_line) smallline.extra['ddd:collider'] = False smallline.extra['ddd:shadows'] = False item.append(smallline) if width > 14.0: largearea = ddd.line([[0, -6.75], [1.575, -6.75]]).arc_to([1.575, 6.75], center=[1.575, 0], ccw=True).line_to([0, 6.75]) if side == 1: largearea = largearea.rotate(math.pi) largearea = largearea.translate([side * length_l / 2, 0]) largearea = largearea.buffer( line_width, cap_style=ddd.CAP_SQUARE).triangulate().material( ddd.mats.painted_line) largearea.extra['ddd:collider'] = False largearea.extra['ddd:shadows'] = False item.append(largearea) goal = basketball_hoop().rotate(ddd.ROT_TOP_CCW) if side == 1: goal = goal.rotate(ddd.ROT_TOP_HALFTURN) goal = goal.translate([side * (length_l / 2 - 1.22 - 0.15), 0, 0]) item.append(goal) item = ddd.uv.map_cubic(item) #hoop = basketball_hoop(width=width + 0.5, net_height_center=net_height_center, net_height_post=net_height_post) #item.append(hoop) return item
def tennis_field_lines(length=23.77, width=10.97, square_length_ratio=6.40 / 23.77, net_height_center=0.914, net_height_post=1.07, line_width=0.10): """ Playground fields are seen x: length, y: width doubles_width_official = 10.97 singles_width_official = 8.23 extra_width = 3.65 (/2?) extra_length = 6.50 (/2?) """ length = min(length, 23.77) width = min(width, 10.97) (length, width) = enforce_aspect_ratio(length, width, 23.77 / 10.97) item = ddd.group3(name="Tennis lines") rectangle = ddd.rect([-length / 2, -width / 2, length / 2, width / 2]) coords = rectangle.geom.exterior.coords width_seg = ddd.line([coords[0], coords[1]]) length_seg = ddd.line([coords[1], coords[2]]) width2_seg = ddd.line([coords[2], coords[3]]) length2_seg = ddd.line([coords[3], coords[0]]) width_l = width_seg.geom.length length_l = length_seg.geom.length exterior = rectangle.outline().buffer( line_width, cap_style=ddd.CAP_SQUARE).triangulate().material(ddd.mats.painted_line) exterior.name = "Bounds line" exterior.extra['ddd:collider'] = False exterior.extra['ddd:shadows'] = False item.append(exterior) centralline_2d = ddd.line( [width_seg.geom.centroid, width2_seg.geom.centroid], name="Central line") goal_width = 4.88 # Sideline sideline_pos_ratio = 8.23 / 10.97 for side in (-1, 1): sideline = centralline_2d.translate( [0, side * width * sideline_pos_ratio * 0.5]) sideline = sideline.buffer( line_width, cap_style=ddd.CAP_SQUARE).triangulate().material( ddd.mats.painted_line) sideline.extra['ddd:collider'] = False sideline.extra['ddd:shadows'] = False item.append(sideline) for side in (-1, 1): midline = ddd.line([[ side * length * square_length_ratio, -width * sideline_pos_ratio * 0.5 ], [ side * length * square_length_ratio, width * sideline_pos_ratio * 0.5 ]]) midline = midline.buffer( line_width, cap_style=ddd.CAP_SQUARE).triangulate().material( ddd.mats.painted_line) midline.extra['ddd:collider'] = False midline.extra['ddd:shadows'] = False item.append(midline) item = ddd.uv.map_cubic(item) net = tennis_net(width=width + 0.5, net_height_center=net_height_center, net_height_post=net_height_post) item.append(net) return item
def handball_field_lines(length=40.0, width=20.0, line_width=0.10): """ """ item = ddd.group3(name="Handball field") length = min(length, 40) width = min(width, 20) (length, width) = enforce_aspect_ratio(length, width, 40 / 20) rectangle = ddd.rect([-length / 2, -width / 2, length / 2, width / 2]) coords = rectangle.geom.exterior.coords width_seg = ddd.line([coords[0], coords[1]]) length_seg = ddd.line([coords[1], coords[2]]) width2_seg = ddd.line([coords[2], coords[3]]) length2_seg = ddd.line([coords[3], coords[0]]) width_l = width_seg.geom.length length_l = length_seg.geom.length exterior = rectangle.outline().buffer( line_width, cap_style=ddd.CAP_SQUARE).triangulate().material(ddd.mats.painted_line) exterior.name = "Bounds line" exterior.extra['ddd:collider'] = False exterior.extra['ddd:shadows'] = False midline_2d = ddd.line( [length_seg.geom.centroid, length2_seg.geom.centroid], name="Mid line") midline = midline_2d.buffer( line_width, cap_style=ddd.CAP_SQUARE).triangulate().material(ddd.mats.painted_line) midline.extra['ddd:collider'] = False midline.extra['ddd:shadows'] = False #midcircle_radius_ratio = 9.15 / 67.5 #midcircle = ddd.disc(center=midline_2d.geom.centroid.coords, r=width_l * midcircle_radius_ratio).outline() #midcircle = midcircle.buffer(line_width, cap_style=ddd.CAP_SQUARE).triangulate().material(ddd.mats.painted_line) #midcircle.extra['ddd:collider'] = False #midcircle.extra['ddd:shadows'] = False item.append(exterior) item.append(midline) #item.append(midcircle) centralline_2d = ddd.line( [width_seg.geom.centroid, width2_seg.geom.centroid], name="Central line") goal_width = 3 smallarea_width_ratio = (goal_width + 5.5 * 2) / 67.5 smallarea_length_ratio = 5.5 / 67.5 largearea_width_ratio = 40.3 / 67.5 largearea_length_ratio = 16.5 / 105.5 for side in (-1, 1): if width > (1.5 + 6) * 2: smallarea = ddd.point([0, -1.5 - 6]).arc_to([6, -1.5], [0, -1.5], True) smallarea = smallarea.line_to([6, 1.5]).arc_to([0, 1.5 + 6], [0, 1.5], True) if side == 1: smallarea = smallarea.rotate(math.pi) smallarea = smallarea.translate([side * length_l / 2, 0]) smallarea = smallarea.buffer( line_width, cap_style=ddd.CAP_SQUARE).triangulate().material( ddd.mats.painted_line) smallarea.extra['ddd:collider'] = False smallarea.extra['ddd:shadows'] = False item.append(smallarea) if width > (8 * 2): largearea = ddd.point([0, -1.5 - 9]).arc_to([9, -1.5], [0, -1.5], True) largearea = largearea.line_to([9, 1.5]).arc_to([0, 1.5 + 9], [0, 1.5], True) if side == 1: largearea = largearea.rotate(math.pi) largearea = largearea.translate([side * length_l / 2, 0]) largearea = largearea.buffer(line_width, cap_style=ddd.CAP_SQUARE) largearea = largearea.intersection(rectangle) largearea = largearea.triangulate().material( ddd.mats.painted_line ) # TODO: use sketchy line material with discontinuous line largearea.extra['ddd:collider'] = False largearea.extra['ddd:shadows'] = False item.append(largearea) # TODO: shall depend on the soccer type, assign earlier maybe if width > 10: goal = football_goal(width=3.0, height=2) else: goal = football_goal_small() goal = goal.rotate(ddd.ROT_TOP_CCW) if side == 1: goal = goal.rotate(ddd.ROT_TOP_HALFTURN) goal = goal.translate([side * length_l / 2, 0, 0]) item.append(goal) item = ddd.uv.map_cubic(item) return item
def solids_borders(root, pipeline, obj): floors = ddd.group2(name="Floors") ceilings = ddd.group2(name="Ceilings") walls = ddd.group2(name="Walls") angles_ceiling = [-math.pi / 4, math.pi / 4] angles_floor1 = [math.pi / 4 * 3, math.pi / 4 * 4] angles_floor2 = [-math.pi / 4 * 4, -math.pi / 4 * 3] polygon = obj.geom.exterior if polygon.is_ccw: polygon.coords = reversed(list(polygon.coords)) segments = zip(polygon.coords, polygon.coords[1:] + polygon.coords[:1]) for a, b in segments: angle = math.atan2(b[1] - a[1], b[0] - a[0]) if (angle > angles_floor1[0] and angle < angles_floor1[1]): floors.append(ddd.line([a, b])) if (angle > angles_floor2[0] and angle < angles_floor2[1]): floors.append(ddd.line([a, b])) if (angle > angles_ceiling[0] and angle < angles_ceiling[1]): ceilings.append(ddd.line([a, b])) ddd.mats.grass = ddd.material( name="Grass", color='#2dd355', texture_path="res://assets/scene/props/grass-texture-tiled.png", #alpha_cutoff=0.05, #extra={'ddd:collider': False, 'ddd:shadows': False, 'uv:scale': 0.05} ) floor_lines = floors floors = ddd.line([[0, 0], [1, 1]], name="Floors") floors.geom = linemerge([g.geom for g in floor_lines.children]) # Iterate merged lines floors = floors.individualize(always=True) for line in floors.children: floor = line.buffer(10.0) floor = floor.material(ddd.mats.grass) floor.extra['floor_line'] = line floor.extra['ddd:z_index'] = 40 floor = uvmapping.map_2d_path(floor, line, line_x_offset=64.0, line_x_width=64.0) #ddd.trace(locals()) print(floor.get('uv', None)) line.replace(floor) #floors.extra['ddd:z_index'] = 40 #newobj = ddd.group2([obj, floors], name="Solid") #obj.replace(newobj) root.find("/Rooms").append(floors) obj.extra['floors'] = floors ceiling_lines = ceilings lines = linemerge([g.geom for g in ceiling_lines.children]) ceilings = DDDObject2(name="Ceilings", geom=lines) ceilings.extra['ddd:z_index'] = 40 ceilings = ceilings.individualize(always=True).clean() for pc in ceilings.children: c = pc.copy() c.extra['ceiling_line'] = c.copy() c = c.buffer(15.0) c = filters.noise_random(c, scale=10.0) pc.replace(c) ceilings = ceilings.material(ddd.mats.bricks) ceilings.mat.color_rgba[3] = 128 ceilings = ceilings.clean() #newobj = ddd.group2([obj, floors], name="Solid") #obj.replace(newobj) root.find("/Rooms").append(ceilings) obj.extra['ceilings'] = ceilings
def football_field_lines(length=105.0, width=67.5, line_width=0.10): """ Playground fields are seen x: length, y: width """ item = ddd.group3(name="Football lines") rectangle = ddd.rect([-length / 2, -width / 2, length / 2, width / 2]) coords = rectangle.geom.exterior.coords width_seg = ddd.line([coords[0], coords[1]]) length_seg = ddd.line([coords[1], coords[2]]) width2_seg = ddd.line([coords[2], coords[3]]) length2_seg = ddd.line([coords[3], coords[0]]) width_l = width_seg.geom.length length_l = length_seg.geom.length exterior = rectangle.outline().buffer( line_width, cap_style=ddd.CAP_SQUARE).triangulate().material(ddd.mats.painted_line) exterior.name = "Bounds line" exterior.extra['ddd:collider'] = False exterior.extra['ddd:shadows'] = False midline_2d = ddd.line( [length_seg.geom.centroid, length2_seg.geom.centroid], name="Mid line") midline = midline_2d.buffer( line_width, cap_style=ddd.CAP_SQUARE).triangulate().material(ddd.mats.painted_line) midline.extra['ddd:collider'] = False midline.extra['ddd:shadows'] = False midcircle_radius_ratio = 9.15 / 67.5 midcircle = ddd.disc(center=midline_2d.geom.centroid.coords, r=width_l * midcircle_radius_ratio).outline() midcircle = midcircle.buffer( line_width, cap_style=ddd.CAP_SQUARE).triangulate().material(ddd.mats.painted_line) midcircle.extra['ddd:collider'] = False midcircle.extra['ddd:shadows'] = False item.append(exterior) item.append(midline) item.append(midcircle) centralline_2d = ddd.line( [width_seg.geom.centroid, width2_seg.geom.centroid], name="Central line") goal_width = 7.2 smallarea_width_ratio = (goal_width + 5.5 * 2) / 67.5 smallarea_length_ratio = 5.5 / 67.5 largearea_width_ratio = 40.3 / 67.5 largearea_length_ratio = 16.5 / 105.5 for side in (-1, 1): smallarea = ddd.line([[0, -1], [1, -1], [1, 1], [0, 1]]) smallarea = smallarea.scale([ smallarea_length_ratio * length, smallarea_width_ratio * width * 0.5 ]) if side == 1: smallarea = smallarea.rotate(math.pi) smallarea = smallarea.translate([side * length_l / 2, 0]) smallarea = smallarea.buffer( line_width, cap_style=ddd.CAP_SQUARE).triangulate().material( ddd.mats.painted_line) smallarea.extra['ddd:collider'] = False smallarea.extra['ddd:shadows'] = False item.append(smallarea) largearea = ddd.line([[0, -1], [1, -1], [1, 1], [0, 1]]) largearea = largearea.scale([ largearea_length_ratio * length, largearea_width_ratio * width * 0.5 ]) if side == 1: largearea = largearea.rotate(math.pi) largearea = largearea.translate([side * length_l / 2, 0]) largearea = largearea.buffer( line_width, cap_style=ddd.CAP_SQUARE).triangulate().material( ddd.mats.painted_line) largearea.extra['ddd:collider'] = False largearea.extra['ddd:shadows'] = False item.append(largearea) # TODO: shall depend on the soccer type, assign earlier maybe if width > 30: goal = football_goal11() elif width > 15: goal = football_goal9() elif width > 9: goal = football_goal7() else: goal = football_goal_small() goal = goal.rotate(ddd.ROT_TOP_CCW) if side == 1: goal = goal.rotate(ddd.ROT_TOP_HALFTURN) goal = goal.translate([side * length_l / 2, 0, 0]) item.append(goal) item = ddd.uv.map_cubic(item) return item
def generate_building_3d_generic(self, building_2d): """ Buildings 2D may contain references to building parts. TODO: Do a lot more in tags in 2D and here, and generalize tasks to pipelines and tags. Support buildings recursively earlier. """ floors = building_2d.extra.get('osm:building:levels', None) floors_min = building_2d.extra.get('osm:building:min_level', 0) if not floors: floors = random.randint(2, 8) floors = int(float(floors)) floors_min = int(float(floors_min)) base_floors = floors base_floors_min = floors_min random.seed(hash(building_2d.name)) building_material = random.choice([ddd.mats.building_1, ddd.mats.building_2, ddd.mats.building_3]) if building_2d.extra.get('osm:building:material', None): material_name = building_2d.extra.get('osm:building:material') if hasattr(ddd.mats, material_name): building_material = getattr(ddd.mats, material_name) entire_building_2d = ddd.group2() entire_building_3d = building_2d.copy3(name="Building: %s" % (building_2d.name)) roof_type = weighted_choice({'none': 2, 'flat': 1, 'pyramidal': 0.5, 'attic': 0.5, 'terrace': 1}) roof_buffered = weighted_choice({True: 1, False: 5}) roof_buffer = random.uniform(0.5, 1.2) roof_wall_material = weighted_choice({"stone": 3, "bricks": 1}) for part in (building_2d.extra.get('ddd:building:parts', []) + [building_2d]): # Process subbuildings recursively (non standard, but improves support and compatibility with other renderers) if part != building_2d and part.extra.get('osm:building', None) is not None: subbuilding = self.generate_building_3d_generic(part) entire_building_2d.append(part) entire_building_3d.append(subbuilding) continue building_3d = None try: floors = int(float(part.extra.get('osm:building:levels', base_floors))) if floors == 0: logger.warn("Building part with 0 floors (setting to 1): %s", floors) floors = 1 floors_min = int(float(part.extra.get('osm:building:min_level', base_floors_min))) # Remove the rest of the building if part == building_2d: part = part.subtract(entire_building_2d) if part.geom.is_empty: continue material = building_material if part.extra.get('osm:building:material', None): material_name = part.extra.get('osm:building:material') if hasattr(ddd.mats, material_name): material = getattr(ddd.mats, material_name) if part.extra.get('osm:building:facade:material', None): material_name = part.extra.get('osm:building:facade:material') if hasattr(ddd.mats, material_name): material = getattr(ddd.mats, material_name) # Roof: default pbuffered = roof_buffered roof_shape = roof_type if floors < 2: roof_shape = 'none' if floors < base_floors: pbuffered = False if (random.uniform(0, 1) < 0.5): roof_shape = random.choice(['terrace', 'none']) if (floors <= 2): if (random.uniform(0, 1) < 0.8): roof_shape = random.choice(['terrace', 'terrace', 'terrace', 'none']) if 'osm:building:part' in part.extra: roof_shape = 'none' pbuffered = 0 # Roof: info roof_shape = part.extra.get('osm:roof:shape', roof_shape) roof_height = float(part.extra.get('osm:roof:height', 0)) roof_material = ddd.mats.roof_tiles if part.extra.get('osm:roof:material', None): material_name = part.extra.get('osm:roof:material') if hasattr(ddd.mats, material_name): roof_material = getattr(ddd.mats, material_name) floors_height = floors * 3.00 floors_min_height = floors_min * 3.00 min_height = float(part.extra.get('osm:min_height', floors_min_height)) #max_height = parse_meters(part.extra.get('osm:height', floors_height + min_height)) - roof_height max_height = parse_meters(part.extra.get('osm:height', floors_height)) - roof_height dif_height = max_height - min_height # Generate building procedurally (use library) try: building_3d = part.extrude(dif_height) except ValueError as e: logger.error("Could not generate building (%s): %s", part, e) continue except DDDException as e: logger.error("Could not generate building (%s): %s", part, e) continue if min_height == 0: building_3d = ddd.meshops.remove_faces_pointing(building_3d, ddd.VECTOR_DOWN) if min_height: building_3d = building_3d.translate([0, 0, min_height]) building_3d = building_3d.material(material) # Building solid post processing if part.extra.get('osm:tower:type', None) == 'bell_tower': # and dif_height > 6: # Cut center_pos = part.centroid().geom.coords[0] (axis_major, axis_minor, axis_rot) = ddd.geomops.oriented_axis(part) cut1 = ddd.rect([-axis_major.length(), -axis_minor.length() * 0.20, +axis_major.length(), +axis_minor.length() * 0.20]) cut2 = ddd.rect([-axis_major.length() * 0.20, -axis_minor.length(), +axis_major.length() * 0.20, +axis_minor.length()]) cuts = ddd.group2([cut1, cut2]).union().rotate(axis_rot).extrude(-6.0).translate([center_pos[0], center_pos[1], max_height - 2]) #ddd.group3([building_3d, cuts]).show() building_3d = building_3d.subtract(cuts) #building_3d.show() # TODO: Create 1D items (axis_major, axis_minor, axis_rot) = ddd.geomops.oriented_axis(part.buffer(-0.80)) for coords in (axis_major.geom.coords[0], axis_major.geom.coords[1], axis_minor.geom.coords[0], axis_minor.geom.coords[1]): bell = urban.bell().translate([coords[0], coords[1], max_height - 3.0]) entire_building_3d.append(bell) # Base if 'osm:building:part' not in part.extra: if random.uniform(0, 1) < 0.2: base = part.buffer(0.3, cap_style=2, join_style=2).extrude(1.00) base = base.material(random.choice([ddd.mats.building_1, ddd.mats.building_2, ddd.mats.building_3, ddd.mats.stone, ddd.mats.cement])) building_3d.children.append(base) # Roof try: roof = None if roof_shape == 'flat': # Flat default_height = 0.75 roof_height = roof_height if roof_height else default_height roof = part.buffer(roof_buffer if pbuffered else 0, cap_style=2, join_style=2).extrude(roof_height).translate([0, 0, max_height]).material(roof_material) elif roof_shape == 'terrace': # Flat usefence = random.uniform(0, 1) < 0.8 if usefence: terrace = part.subtract(part.buffer(-0.4)).extrude(0.6).translate([0, 0, max_height]).material(getattr(ddd.mats, roof_wall_material)) fence = part.buffer(-0.2).outline().extrude(0.7).twosided().translate([0, 0, max_height + 0.6]).material(ddd.mats.railing) roof = ddd.group3([terrace, fence], name="Roof") else: terrace = part.subtract(part.buffer(-0.4)).extrude(random.uniform(0.40, 1.20)).translate([0, 0, max_height]).material(ddd.mats.stone) roof = ddd.group3([terrace], name="Roof") elif roof_shape == 'pyramidal': # Pointy default_height = floors * 0.2 + random.uniform(2.0, 5.0) roof_height = roof_height if roof_height else default_height roof = part.buffer(roof_buffer if pbuffered else 0, cap_style=2, join_style=2).extrude_step(part.centroid(), roof_height) roof = roof.translate([0, 0, max_height]).material(roof_material) elif roof_shape == 'attic': # Attic height = random.uniform(3.0, 4.0) roof = part.buffer(roof_buffer if pbuffered else 0, cap_style=2, join_style=2).extrude_step(part.buffer(-2), height, method=ddd.EXTRUSION_METHOD_SUBTRACT).translate([0, 0, max_height]).material(roof_material) elif roof_shape == 'gabled': # Attic base = part.buffer(roof_buffer if pbuffered else 0) orientation = "major" if part.extra.get("osm:roof:orientation", "along") == "across": orientation = "minor" (axis_major, axis_minor, axis_rot) = ddd.geomops.oriented_axis(base) axis_line = axis_major if orientation == "major" else axis_minor default_height = random.uniform(3.0, 4.0) roof_height = roof_height if roof_height else default_height roof = base.extrude_step(axis_line, roof_height).translate([0, 0, max_height]).material(roof_material) ''' #elif roof_shape == 'round': # Attic base = part.buffer(roof_buffer if pbuffered else 0) orientation = "major" if part.extra.get("osm:roof:orientation", "along") == "across": orientation = "minor" (axis_major, axis_minor, axis_rot) = ddd.geomops.oriented_axis(base) axis_line = axis_major if orientation == "major" else axis_minor major_seg_plus = ((axis_major.coords[0][0] + (axis_minor.coords[0][0] - axis_minor.coords[1][0]) * 0.5, axis_major.coords[0][1] + (axis_minor.coords[0][1] - axis_minor.coords[1][1]) * 0.5), (axis_major.coords[1][0] + (axis_minor.coords[0][0] - axis_minor.coords[1][0]) * 0.5, axis_major.coords[1][1] + (axis_minor.coords[0][1] - axis_minor.coords[1][1]) * 0.5)) minor_seg_plus = ((axis_minor.coords[0][0] + (axis_major.coords[0][0] - axis_major.coords[1][0]) * 0.5, axis_minor.coords[0][1] + (axis_major.coords[0][1] - axis_major.coords[1][1]) * 0.5), (axis_minor.coords[1][0] + (axis_major.coords[0][0] - axis_major.coords[1][0]) * 0.5, axis_minor.coords[1][1] + (axis_major.coords[0][1] - axis_major.coords[1][1]) * 0.5)) default_height = random.uniform(3.0, 4.0) roof_height = roof_height if roof_height else default_height roof = base.extrude_step(axis_line, roof_height).translate([0, 0, max_height]).material(roof_material) ''' elif roof_shape == 'skillion': # Attic base = part.buffer(roof_buffer if pbuffered else 0) orientation = "major" if part.extra.get("osm:roof:orientation", "along") == "across": orientation = "minor" (axis_major, axis_minor, axis_rot) = ddd.geomops.oriented_axis(base) axis_major = axis_major.geom axis_minor = axis_minor.geom major_seg_plus = ((axis_major.coords[0][0] + (axis_minor.coords[0][0] - axis_minor.coords[1][0]) * 0.5, axis_major.coords[0][1] + (axis_minor.coords[0][1] - axis_minor.coords[1][1]) * 0.5), (axis_major.coords[1][0] + (axis_minor.coords[0][0] - axis_minor.coords[1][0]) * 0.5, axis_major.coords[1][1] + (axis_minor.coords[0][1] - axis_minor.coords[1][1]) * 0.5)) minor_seg_plus = ((axis_minor.coords[0][0] + (axis_major.coords[0][0] - axis_major.coords[1][0]) * 0.5, axis_minor.coords[0][1] + (axis_major.coords[0][1] - axis_major.coords[1][1]) * 0.5), (axis_minor.coords[1][0] + (axis_major.coords[0][0] - axis_major.coords[1][0]) * 0.5, axis_minor.coords[1][1] + (axis_major.coords[0][1] - axis_major.coords[1][1]) * 0.5)) skillion_line = major_seg_plus if orientation == "major" else minor_seg_plus default_height = random.uniform(1.0, 2.0) roof_height = roof_height if roof_height else default_height roof = base.extrude_step(ddd.line(skillion_line), roof_height).translate([0, 0, max_height]).material(roof_material) elif roof_shape == 'hipped': # Attic base = part.buffer(roof_buffer if pbuffered else 0) orientation = "major" if part.extra.get("osm:roof:orientation", "along") == "across": orientation = "minor" (axis_major, axis_minor, axis_rot) = ddd.geomops.oriented_axis(base) axis_line = axis_major if orientation == "major" else axis_minor #other_axis_line = axis_minor if orientation == "major" else axis_major axis_line = axis_line.intersection(axis_line.centroid().buffer(axis_minor.geom.length / 2, cap_style=ddd.CAP_ROUND, resolution=8)) default_height = random.uniform(1.0, 2.0) roof_height = roof_height if roof_height else default_height roof = base.extrude_step(axis_line, roof_height).translate([0, 0, max_height]).material(roof_material) elif roof_shape == 'dome': default_height = random.uniform(2.0, 4.0) roof_height = roof_height if roof_height else default_height roofbase = part.buffer(roof_buffer if pbuffered else 0, cap_style=2, join_style=2) roof = roofbase.copy() steps = 6 stepheight = 1.0 / steps for i in range(steps): stepy = (i + 1) * stepheight stepx = math.sqrt(1 - (stepy ** 2)) stepbuffer = -(1 - stepx) roof = roof.extrude_step(roofbase.buffer(stepbuffer * roof_height), stepheight * roof_height) roof = roof.translate([0, 0, max_height]).material(roof_material) elif roof_shape == 'none': pass else: logger.warning("Unknown roof shape: %s", roof_shape) if roof: building_3d.children.append(roof) except Exception as e: logger.warning("Cannot generate roof: %s (geom: %s)" % (e, part.geom)) # UV Mapping building_3d = ddd.uv.map_cubic(building_3d) entire_building_2d.append(part) entire_building_3d.append(building_3d) except ValueError as e: logger.error("Cannot generate building part %s: %s (geom: %s)" % (part, e, part.geom)) raise #return None except IndexError as e: logger.error("Cannot generate building part %s: %s (geom: %s)" % (part, e, part.geom)) raise #return None except Exception as e: logger.error("Cannot generate building part %s: %s (geom: %s)" % (part, e, part.geom)) raise entire_building_3d.extra['building_2d'] = building_2d entire_building_3d.extra['ddd:building:feature'] = building_2d return entire_building_3d
def pipeline_start(pipeline, root): """ Generate different geometric objects. """ items = ddd.group3() # Remember to use JOIN_ROUND so resolution is applied when buffering points fig = ddd.point([0, 0]).buffer(1.0, resolution=2, join_style=ddd.JOIN_ROUND, cap_style=ddd.CAP_ROUND).triangulate() items.append(fig) fig = ddd.point([0, 0]).buffer(1.0, resolution=3, join_style=ddd.JOIN_ROUND, cap_style=ddd.CAP_ROUND).triangulate() items.append(fig) fig = ddd.point([0, 0]).buffer(1.0, resolution=4, join_style=ddd.JOIN_ROUND, cap_style=ddd.CAP_ROUND).triangulate() items.append(fig) # Extrusion with optional caps fig = ddd.disc().extrude(5) items.append(fig) fig = ddd.disc().extrude(5, base=False) items.append(fig) fig = ddd.disc().extrude(5, cap=False) items.append(fig) fig = ddd.disc().extrude(5, cap=False, base=False) items.append(fig) # Extrude line (to faces, not volume) fig1 = ddd.line([[-2, 0], [0, 0], [2, 2]]) fig = fig1.extrude(2.0).twosided() items.append(fig) # Extrusion to line (explicit) fig1 = ddd.rect([-4, -2, 4, 2]) fig2 = ddd.line([[-4, 0], [4, 0]]) fig = fig1.extrude_step(fig2, 1.0) items.append(fig) # Extrusion to line (explicit) fig1 = ddd.rect([-4, -2, 4, 2]) fig2 = ddd.line([[-3, 0], [3, 0]]) fig = fig1.extrude_step(fig2, 1.0) items.append(fig) # Extrusion to line (explicit, method subtract) fig1 = ddd.rect([-4, -2, 4, 2]) fig2 = ddd.line([[-3, 0], [3, 0]]) fig = fig1.extrude_step(fig2, 1.0, method=ddd.EXTRUSION_METHOD_SUBTRACT) # TODO: this currently fails but should be fixed items.append(fig) # Extrusion to line with vertical (explicit) for skillion roofs fig1 = ddd.rect([-4, -2, 4, 2]) fig2 = ddd.line([[-4, 2], [4, 2]]) fig = fig1.extrude_step(fig2, 1.0) # TODO: this currently fails but should be fixed items.append(fig) # Extrusion to line (axis middle) fig1 = ddd.rect([-4, -2, 4, 2]) #.rotate(math.pi * 1.5) axis_major, axis_minor, axis_angle = ddd.geomops.oriented_axis(fig1) fig = fig1.extrude_step(axis_minor, 1.0) items.append(fig) # Extrusion to line (axis middle) fig1 = ddd.rect([-4, -2, 4, 2]) #.rotate(math.pi * 1.5) axis_major, axis_minor, axis_angle = ddd.geomops.oriented_axis(fig1) fig = fig1.extrude_step(axis_major, 1.0) items.append(fig) # Extrusion to line (buffered geometry) - currently fails (shapely does not return the reduced polygon linestring) fig1 = ddd.rect([-4, -2, 4, 2]) fig = fig1.extrude_step(fig1.buffer(-2.5), 1.0) items.append(fig) # Extrusion to line (buffered geometry) and back (fails, extrusion from point to shape) fig1 = ddd.rect([-4, -2, 4, 2]) fig = fig1.extrude_step(fig1.buffer(-2.5), 1.0) fig = fig.extrude_step(fig1, 1.0) items.append(fig) # Triangulation with hole fig1 = ddd.rect([-4, -2, 4, 2]) fig2 = ddd.rect([-3, -1, -1, 1]) fig = fig1.subtract(fig2).triangulate() items.append(fig) # Extrusion with hole fig1 = ddd.rect([-4, -2, 4, 2]) fig2 = ddd.rect([-3, -1, -1, 1]) fig = fig1.subtract(fig2).extrude(1.0) items.append(fig) # Extrusion with steps with hole fig1 = ddd.rect([-4, -2, 4, 2]) fig2 = ddd.rect([-3, -1, -1, 1]) figh = fig1.subtract(fig2) fig = figh.extrude_step(figh, 1.0, base=False) fig = fig.extrude_step(figh.buffer(-0.25), 1.0) items.append(fig) # Extrusion with steps with hole 2 fig1 = ddd.rect([-4, -2, 4, 2]) fig2 = ddd.rect([-3, -1, -1, 1]) figh = fig1.subtract(fig2) fig = figh.extrude_step(figh, 1.0, base=False, method=ddd.EXTRUSION_METHOD_SUBTRACT) fig = fig.extrude_step(figh.buffer(-0.25), 1.0, method=ddd.EXTRUSION_METHOD_SUBTRACT) items.append(fig) # Simple extrusion fig = ddd.point([0, 0]).buffer(1.0, cap_style=ddd.CAP_ROUND).extrude(5.0) items.append(fig) # Simple extrusion fig = ddd.regularpolygon(5).extrude(5.0) items.append(fig) # Simple extrusion no caps fig = ddd.point([0, 0]).buffer(1.0, cap_style=ddd.CAP_ROUND) fig = fig.extrude_step(fig, 5.0, base=False, cap=False) items.append(fig) # Extrusion between shapes fig1 = ddd.point([0, 0]).buffer(1.0) fig2 = ddd.point([0, 0]).buffer(1.0, cap_style=ddd.CAP_ROUND) fig3 = ddd.point([0, 0]).buffer(1.0) fig = fig1.extrude_step(fig2, 3.0).extrude_step(fig3, 2.0) items.append(fig) # Extrusion fig = ddd.point([0, 0]).buffer(1.0) for i in range(10): fign = ddd.point([0, 0]).buffer(1.0).rotate(math.pi / 12 * i) fig = fig.extrude_step(fign, 0.5) items.append(fig) # Pointy end fig = ddd.point().buffer(2.0, cap_style=ddd.CAP_ROUND) fig = fig.extrude_step(ddd.point(), 5.0) items.append(fig) # Convex shapes (this fails) coords = [[10, 10], [5, 9], [3, 12], [1, 5], [-8, 0], [10, 0]] #coords.reverse() fig = ddd.polygon(coords).scale(0.25) fig = fig.extrude_step(fig.buffer(-0.5), 1) items.append(fig) # Convex shapes - subtract method (works) coords = [[10, 10], [5, 9], [3, 12], [1, 5], [-8, 0], [10, 0]] #coords.reverse() fig = ddd.polygon(coords).scale(0.25) fig = fig.extrude_step(fig.buffer(-0.5), 1, method=ddd.EXTRUSION_METHOD_SUBTRACT) items.append(fig) # Extrude-subtract to bigger fig = ddd.point([0, 0]).buffer(1.0, cap_style=ddd.CAP_ROUND) fig = fig.extrude_step(fig.buffer(1.0), 5.0, method=ddd.EXTRUSION_METHOD_SUBTRACT) items.append(fig) # Extrude-subtract downwards shape = ddd.disc().scale([3, 2]) fig = shape.extrude_step(shape.buffer(-0.5), -1.0, base=False, method=ddd.EXTRUSION_METHOD_SUBTRACT) fig = fig.extrude_step(shape.buffer(-1.0), -0.5, method=ddd.EXTRUSION_METHOD_SUBTRACT) items.append(fig) # Extrude-subtract vertical case fig = ddd.point([0, 0]).buffer(1.0, cap_style=ddd.CAP_ROUND) fig = fig.extrude_step(fig, 5.0, method=ddd.EXTRUSION_METHOD_SUBTRACT) items.append(fig) # Convex shapes with holes - subtract method fig = ddd.group3() text = Text3D.quick_text("86A").scale(2.0) for f in text.children: #f.replace(f.subtract(f.buffer(-0.2))) fe = f.extrude_step(f.buffer(-0.05), 0.2, method=ddd.EXTRUSION_METHOD_SUBTRACT) fig.append(fe) items.append(fig) # Extrude to point fig = ddd.point([0, 0]).buffer(1.0, cap_style=ddd.CAP_ROUND) fig = fig.extrude_step(fig.centroid(), 2.0) items.append(fig) """ fig = ddd.point([0, 0]).buffer(1.0, cap_style=ddd.CAP_ROUND) fig = fig.extrude_step(fig.centroid(), 2.0, method=ddd.EXTRUSION_METHOD_SUBTRACT) items.append(fig) """ # Extrude to empty fig = ddd.point([0, 0]).buffer(1.0, cap_style=ddd.CAP_ROUND) fig = fig.extrude_step(fig.buffer(-2.0), 2.0) items.append(fig) fig = ddd.point([0, 0]).buffer(1.0, cap_style=ddd.CAP_ROUND) fig = fig.extrude_step(fig.buffer(-2.0), 2.0, base=False, method=ddd.EXTRUSION_METHOD_SUBTRACT) items.append(fig) # Extrude with division fig1 = ddd.disc().translate([1.5, 0]).union(ddd.disc()) fig = fig1.extrude_step(fig1.buffer(-0.2), 0.5, method=ddd.EXTRUSION_METHOD_SUBTRACT) fig = fig.extrude_step(fig1.buffer(-0.5), 0.5, method=ddd.EXTRUSION_METHOD_SUBTRACT) items.append(fig) # Extrude multiple with empty geometry fig1 = ddd.point([0, 0]).buffer(2.0, cap_style=ddd.CAP_ROUND) fig = fig1.extrude_step(fig1.buffer(-0.5), 0.5, method=ddd.EXTRUSION_METHOD_SUBTRACT) fig = fig.extrude_step(fig1.buffer(-1.5), 0.5, method=ddd.EXTRUSION_METHOD_SUBTRACT) fig = fig.extrude_step(fig1.buffer(-2.5), 0.5, method=ddd.EXTRUSION_METHOD_SUBTRACT) fig = fig.extrude_step(fig1.buffer(-2.5), 0.5, method=ddd.EXTRUSION_METHOD_SUBTRACT) items.append(fig) # Triangulate/Extrude with colinear segments fig1 = ddd.polygon([[0, 0], [1, 0], [2, 0], [2, 1], [1, 1], [0, 1]]) #fig = fig1.triangulate() fig = fig1.extrude(1.0) items.append(fig) # All items items = ddd.align.grid(items, space=10.0) #items.append(ddd.helper.all()) items.show() root.append(items)
def snap_to_building(self, item_3d, building_3d): # Find building segment to snap item_1d = item_3d.extra.get('ddd:item', None) building_2d = building_3d.get('ddd:building:parent', building_3d.get('ddd:building:building_2d', None)) if building_2d: building_2d = building_2d.get('osm:original', building_2d) #building_2d = item_3d.get('ddd:building').get('ddd:building:building_2d', None) logger.debug("Snapping %s to %s, using 2D %s", item_3d, building_3d, building_2d) if building_2d is None: logger.error("Could not find linked building 2D geometry to snap item %s to building %s.", item_3d, building_3d) return None if building_2d.is_empty(): logger.warn("Cannot snap item to empty geometry: %s", building_3d) return None ''' if building_2d.geom is None: logger.warn("Cannot snap item to building with no geometry (fixme: building was not considered empty though): %s", building_2d) return None if building_2d.geom.type == "MultiPolygon": logger.warn("Cannot snap to MultiPolygon building (ignoring item_3d) TODO: usecommon snap functions which should support MultiPolygon") return None ''' #lines = building_2d.individualize() # geom.exterior # Project only to facade lines building_2d_margin = building_2d.union().buffer(-1) lines = [] for b in [building_3d] + building_3d.children: for s in b.get('ddd:building:segments', []): l = ddd.line([s.p1, s.p2], ) #l.set('ddd:building:segment') = s if s.facade_type != 'contact' and not building_2d_margin.contains(l): lines.append(l) if len(lines) == 0: logger.error("No segments geometry to snap item %s to building %s.", item_3d, building_3d) building_3d.dump() return None lines = ddd.group2(lines) closest_point, segment_idx, segment_coords_a, segment_coords_b, closest_object, closest_object_d = lines.closest_segment(item_1d.centroid()) dir_ver = (segment_coords_b[0] - segment_coords_a[0], segment_coords_b[1] - segment_coords_a[1]) dir_ver_length = math.sqrt(dir_ver[0] ** 2 + dir_ver[1] ** 2) dir_ver = (dir_ver[0] / dir_ver_length, dir_ver[1] / dir_ver_length) angle = math.atan2(dir_ver[1], dir_ver[0]) + math.pi # Reverse angle if point is inside #if building_2d.contains(item_1d.centroid()): #if not building_2d.geom.exterior.is_ccw: #logger.debug("Amenity: %s Closest point: %s Closest Segment: %s Angle: %s" % (amenity.geom.centroid, closest_point, closest_segment, angle)) target_point = closest_point # Fit width in segment # TODO: Move to "snap/align" and make reusable bounds = item_3d.bounds() width = abs(bounds[0][0] - bounds[1][0]) segment_d = math.sqrt((closest_point[0] - segment_coords_a[0]) ** 2 + (closest_point[1] - segment_coords_a[1]) ** 2) #item_3d.set('debug:segment_d_before', segment_d, children=True) min_d = width / 2 max_d = dir_ver_length - min_d if max_d < min_d: # If segment is smaller than width, align to center min_d = dir_ver_length / 2 max_d = dir_ver_length / 2 if segment_d < min_d or segment_d > max_d: segment_d = max(min_d, min(max_d, segment_d)) target_point = (segment_coords_a[0] + dir_ver[0] * segment_d, segment_coords_a[1] + dir_ver[1] * segment_d) #item_3d.set('debug:segment_d_after', segment_d, children=True) #item_3d.set('debug:width', width, children=True) #item_3d.set('debug:min_d', min_d, children=True) #item_3d.set('debug:max_d', max_d, children=True) #item_3d.set('debug:dir_ver_length', dir_ver_length, children=True) # Align rotation item_3d = item_3d.rotate([0, 0, angle]) # + math.pi / 2.0 item_3d = item_3d.translate([target_point[0], target_point[1], 0]) # Raise to floor level, since currently items are not considered by floor point_elevation = terrain.terrain_geotiff_elevation_value(item_1d.centroid().geom.coords[0], self.osm.ddd_proj) extra_elevation = point_elevation - building_3d.get('ddd:building:elevation:min') item_3d = item_3d.translate([0, 0, extra_elevation]) return item_3d
def process_building_segment_analyze(self, part, segment, buildings_ref, ways_ref): """ Analyze a segment of a building, resolving: - Forward object/way/area (ways only?): distance + link + type # not so significative if a single ray is cast from center, line cast? (?) - Segment type: interior, detail, facade.... # interiors may be missed if using single ray cast from segment center - Type of convex hull segment: facade_main, facade_secondary, interior """ # Check if segment touches another segment in this or other building ''' contacts = part.get('ddd:building:contacts') seg_vert_idx_a = segment.seg_idx seg_vert_idx_b = (segment.seg_idx + 1) % (len(part.vertex_list()) - 1) contact_a = contacts.get(seg_vert_idx_a, None) contact_b = contacts.get(seg_vert_idx_b, None) if contact_a and contact_b and contact_a.other == contact_b.other: coa = contact_a.other_idx cob = contact_b.other_idx # Check that segment is contiguous on the "other" geometry (note it can also cycle around vertex list) if abs(coa - cob) == 1 or (abs(coa - cob) == len(contact_a.other.vertex_list()) - 2): segment.contact = contact_a.other ''' othersegments = self._segment_cache[segment.vertex_key()] othersegments = [s for s in othersegments if s.building != part] if othersegments: segment.contact = othersegments[0] if len(othersegments) > 1: logger.warn( "Building segment with several contacts to other buildings, using only first contact." ) # Get the closest (forward) way to the segment, seg_center = (np.array(segment.p2) + np.array(segment.p1)) / 2 point = ddd.point(seg_center) if not ways_ref.is_empty(): try: coords_p, segment_idx, segment_coords_a, segment_coords_b, closest_obj, closest_d = ways_ref.closest_segment( point) segment.closest_way = closest_obj # Check if there is another building between building segment and way ray = ddd.line([seg_center, coords_p]).line_substring(1.0, -1.0) segment.building_front = buildings_ref.intersects(ray) except DDDException as e: logger.warn("Cannot find closest to building segment %s: %s", segment, e) pass # Facade classification # TODO: Do this in styling, study cases if segment.contact: segment.facade_type = 'contact' elif segment.building_front: # TODO: Check distance too segment.facade_type = 'lateral' # / vieable/non-viewable else: # TODO: Check way weights for main/secondary facades segment.facade_type = 'main' # secondary / lateral / back