diff --git a/README.md b/README.md index 0415632..f2357b1 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,140 @@ # JARCH-Vis -JARCH Vis is add-on for Blender 3D that helps create commonly used objects for architectural visualization. -Some of these objects are: + +## Introduction +JARCH Vis is add-on for Blender 3D that helps create certain +architectural objects in a way that makes them easily customizable. + +The architecture types that can be created are: + * Siding * Flooring * Windows -* Stairs * Roofing + +## Usage +The JARCH Vis (JV) panel is found in the 3D Viewport Toolbar, under a +section called "JARCH Vis." There is always at least four buttons within +that panel, allowing an object of each architecture type to be easily +added to the scene. If a JV object is selected, then the options +specific to the architecture type of the selected object will be +displayed in the panel. + +Modifying any value within the panel will automatically update the mesh +object, assuming that the `Update Automatically?` option is selected. If +that option is unchecked, then the `Update Object` button has to be +clicked to get the object to update. If the mesh object is really large +or complicated, then it might be a good idea to not update automatically. + +#### Converting Objects +There are times when you might want to add objects that aren't +rectangular. This can be done by converting an object made up of planes +into a JV object. To convert an object, start by entering editmode. +Then, select all faces that are in the same plane/group and click the `+` +button in the JV panel. Repeat for all groups of faces. + +##### Face Group +> In JARCH Vis, a face group is a collection of faces that all lie in +the same plane and will be used as a mask/boolean object for a JV object. +Essentially, a face group is used as a cookie-cutter. It is important +that all points lie in the same plane within a face group to ensure +expected results. + +JV has two ways to use the face groups as cutter objects. One way is with +the boolean modifier, which supports non-convex face groups. However, +the boolean modifier sometimes returns odd results. If the face group +is convex, then check the `Convex?` option, which will cause JV to use a +different method of cutting the face group. + +##### Convex +> A shape or polygon is convex if there does not exist a line segment +between two points on the surface that passes outside of the surface. +More specifically, a face group is convex if all interior angles between +boundary edges are less than or equal to 180 degrees and there are no +parts missing from the interior of the shape. + +Once all face groups have been added, exit editmode and make sure that +the rotation and scale have been applied for the object: `CTRL+A -> +Rotation & Scale`. After that, just click `Convert Object` within the JV +panel. The newly converted JV object will have most of the same options +as a regular JV object, except the overall dimension values. + +##### Cutouts +Cutouts provide an easy way to cut rectangles out of siding, roofing, and +flooring objects. Cutouts can even be added to objects that have been +converted. To add a cutout, first check the `Cutouts?` box, then click +`Add Cutout`. Every cutout has the following options: + +* `Location` - the X, Y, and Z position of the corner of the cutout +* `Dimensions` - the X, Y, and Z dimensions of the cutout +* `Rotation` - the X, Y, and Z rotation of the cutout +* `Local` - whether or not the location and rotation of the cutout are in +local coordinates or not + +A cutout can be removed by clicking the `Remove Cutout` button right below +the `Local` option. + +> The `Local` option for a cutout can dramatically change where the +cutout is located. If `Local` is checked, then the `Location` and `Rotation` +values are offsets from object's origin and rotation. If `Local` is not +checked, then the values are in reference to the world and are absolute +positions. + +#### Materials +The faces within JV objects are created in such a way that materials +can be added easily. By default, all faces will use the material in the +first slot on the object. However, glass and mortar faces will use the +second slot. Therefore adding materials is as easy as putting the primary +material in the first slot, and a secondary material, if needed, in the +second slot. + +#### UV Unwrapping +All JV objects have UV seams added and are unwrapped automatically. + +## Installation +1. Download the latest release or `.zip` version of the +repository. +2. Extract the downloaded folder and rename the it to `jarch_vis`. +Make sure that the file structure is like `jarch_vis/__init__.py` and not +`jarch_vis/some_folder/__init__.py`. +3. Move the `jarch_vis` folder to your Blender install location under +`2.8x/scripts/addons`. On Windows, this is generally found at +`Program Files/Blender Foundation/Blender/2.8x/...` +4. Open Blender, go to `Edit -> Preferences -> Add-ons` and check the +option that is `Add Mesh: JARCH Vis`. + +## Revision Log +### JV 2.0.0 for Blender 2.8x +Version 2.0.0 represents a massive change in JARCH Vis. The entire add-on was re-written from the ground up. Changing the user-interface, +simplifying the backing code, allowing new architecture types to be +added more easily, and updating JV to work with Blender 2.8x. +It is important to note that backwards compatibility has been broken. +JV objects created with previous versions of JV will +not work with this newest version. + +Changes in no particular order include: + +* Stairs have been removed for the time being +* Two new patterns of flooring: Corridor and Octagons +* Hexagons and Octagons can have dots/cubes added between the tiles +* Several flooring patterns were renamed +* A new way of handling cutouts was added that is faster and allows for an +"unlimited" number of cutouts +* Most architecture pattern types are more customizable and many +arbitrary min and max bounds on values have been removed +* Several types of siding have been combined +* Shiplap siding, shakes, and scallop shakes have been added +* Tongue & Groove and Stone siding have been removed. Stone will hopefully +be added back in at some point. Tongue & Groove is likely gone for awhile. +* Shakes for roofing have been added +* Terracotta tiles have been updated to a slightly different look +* Gliding, double-hung, and stationary windows have all been combined +into the "Regular" pattern, with new options added to allow all the old +looks to be created +* Arch and Ellipse windows are built differently to make them smoother +at a lower resolution + +## Goals +1. Add a much more customizable and faster version of stone siding +then previously existed +1. Add stairs back in +1. Add railing \ No newline at end of file diff --git a/__init__.py b/__init__.py index 2669b4f..1eced4b 100644 --- a/__init__.py +++ b/__init__.py @@ -16,86 +16,62 @@ bl_info = { "name": "JARCH Vis", "author": "Jacob Morris", - "version": (1, 0, 1), - "blender": (2, 78, 0), + "version": (2, 0, 0), + "blender": (2, 80, 0), "location": "View 3D > Toolbar > JARCH Vis", "description": "Allows The Creation Of Architectural Objects Like Flooring, Siding, Stairs, Roofing, and Windows", "category": "Add Mesh" - } +} if "bpy" in locals(): import importlib - importlib.reload(jv_properties) - importlib.reload(jv_siding) - importlib.reload(jv_flooring) - importlib.reload(jv_stairs) - importlib.reload(jv_roofing) - importlib.reload(jv_windows) + modules = [ + jv_utils, + jv_types, + jv_properties, + + jv_builder_base, + jv_flooring, + jv_siding, + jv_roofing, + jv_windows, + + jv_operators, + jv_panel + ] + + for module in modules: + importlib.reload(module) else: + from . import jv_utils + from . import jv_types from . import jv_properties - from . import jv_siding + + from . import jv_builder_base from . import jv_flooring - from . import jv_stairs + from . import jv_siding from . import jv_roofing from . import jv_windows -import bpy -from bpy.props import StringProperty, CollectionProperty, IntProperty, FloatProperty - - -class FaceGroup(bpy.types.PropertyGroup): - data = StringProperty() - num_faces = IntProperty() - face_slope = FloatProperty() - rot = FloatProperty(unit="ROTATION") + from . import jv_operators + from . import jv_panel -class CutoutGroup(bpy.types.PropertyGroup): - x_dist = FloatProperty(subtype="DISTANCE") - z_dist = FloatProperty(subtype="DISTANCE") - width = FloatProperty(subtype="DISTANCE") - height = FloatProperty(subtype="DISTANCE") - - -class INFO_MT_mesh_jv_menu_add(bpy.types.Menu): - bl_idname = "INFO_MT_mesh_jv_menu_add" - bl_label = "JARCH Vis" - - def draw(self, context): - layout = self.layout - layout.operator("mesh.jv_flooring_add", text="Add Flooring", icon="MESH_GRID") - layout.operator("mesh.jv_roofing_add", text="Add Roofing", icon="LINCURVE") - layout.operator("mesh.jv_siding_add", text="Add Siding", icon="UV_ISLANDSEL") - layout.operator("mesh.jv_stairs_add", text="Add Stairs", icon="MOD_ARRAY") - layout.operator("mesh.jv_window_add", text="Add Window", icon="OUTLINER_OB_LATTICE") - - -def menu_add(self, context): - self.layout.menu("INFO_MT_mesh_jv_menu_add", icon="PLUGIN") +import bpy def register(): - bpy.utils.register_module(__name__) - bpy.types.INFO_MT_mesh_add.append(menu_add) - bpy.types.Object.jv_face_groups = CollectionProperty(type=FaceGroup) - bpy.types.Object.jv_cutout_groups = CollectionProperty(type=CutoutGroup) - - wm = bpy.context.window_manager - km = wm.keyconfigs.addon.keymaps.new(name="3D View", space_type="VIEW_3D") - km.keymap_items.new("mesh.jv_add_face_group_item", "A", "PRESS", ctrl=True) + jv_properties.register() + jv_operators.register() + jv_panel.register() def unregister(): - bpy.utils.unregister_module(__name__) - del bpy.types.Object.jv_face_groups - del bpy.types.Object.jv_cutout_groups - - wm = bpy.context.window_manager - if wm.keyconfigs.addon: - for kmi in wm.keyconfigs.addon.keymaps['3D View'].keymap_items: - if kmi.idname == "mesh.jv_add_face_group_item": - wm.keyconfigs.addon.keymaps['3D View'].keymap_items.remove(kmi) + jv_panel.unregister() + jv_operators.unregister() + jv_properties.unregister() + if __name__ == "__main__": register() diff --git a/jv_builder_base.py b/jv_builder_base.py new file mode 100644 index 0000000..c07e82e --- /dev/null +++ b/jv_builder_base.py @@ -0,0 +1,545 @@ +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import bpy +import bmesh +from random import uniform +from mathutils import Vector, Euler +from typing import Union, List +from math import radians, atan +from . jv_utils import CuboidalRegion + + +class JVBuilderBase: + is_cutable = False + is_convertible = False + + @staticmethod + def draw(props, layout): + pass + + @staticmethod + def update(props, context): + pass + + @staticmethod + def delete(props, context): + src = props.convert_source_object + + if src is not None: # remove boolean objects if non-convex face groups + for fg in src.jv_properties.face_groups: + if not fg.is_convex: + fg.boolean_object.hide_viewport = False + fg.boolean_object.select_set(True) + + bpy.ops.object.delete() + + if src is not None: + src.hide_viewport = False + src.select_set(True) + context.view_layer.objects.active = src + + @staticmethod + def _start(context): + bm = bmesh.new() + return bm + + @staticmethod + def _uv_unwrap(by_seams=True): + bpy.ops.object.editmode_toggle() + + bpy.ops.mesh.select_all(action="SELECT") + + if by_seams: + bpy.ops.uv.unwrap(method="ANGLE_BASED", margin=0.001) + else: + bpy.ops.uv.smart_project() + + bpy.ops.object.editmode_toggle() + + @staticmethod + def _finish(context, bm: bmesh.types.BMesh): + bm.normal_update() + bm.to_mesh(context.object.data) + bm.free() + + @staticmethod + def _geometry(props, dims: tuple): + return [], [] + + @staticmethod + def _build_mesh_from_geometry(mesh: bmesh.types.BMesh, vertices: list, faces: list): + """ + Take a bmesh mesh, vertices positions, and face-vertex indices and clear and add the vertices and faces + to the mesh object + :param mesh: the bmesh object to clear and add the geometry to + :param vertices: tuples of the positions of the vertices + :param faces: tuples of the indices of the vertices that make up the face + """ + mesh.clear() + for v in vertices: + mesh.verts.new(v) + mesh.verts.ensure_lookup_table() + + for f in faces: + mesh.faces.new([mesh.verts[i] for i in f]) + mesh.faces.ensure_lookup_table() + + @staticmethod + def _solidify(mesh: bmesh.types.BMesh, thickness: Union[callable, float]): + """ + Solidify the mesh. If 'thickness' is callable, then use the normal as the direction + :param mesh: the mesh to solidify + :param thickness: If thickness is callable, then each new face gets a thickness value from the function. + Otherwise, the value will be used consistently. + :return: + """ + mesh.normal_update() + visited = set() + start_th = 0 if callable(thickness) else thickness + + new_geom = bmesh.ops.solidify(mesh, geom=mesh.faces[:], thickness=start_th)["geom"] + + # manually add thickness if 'thickness' is callable + if callable(thickness): + faces = set() + for item in new_geom: + if isinstance(item, bmesh.types.BMFace): + faces.add(item) + + groups = JVBuilderBase._group_connected_faces(faces) + for group in groups: + th = thickness() + for face in group: + for v in face.verts: + if v not in visited: + v.co.x += face.normal[0] * th + v.co.y += face.normal[1] * th + v.co.z += face.normal[2] * th + + visited.add(v) + + return new_geom + + @staticmethod + def _create_variance_function(vary: bool, base_amount: float, variance: float): + variance /= 100 # convert to decimal + + if vary: + return lambda: uniform(base_amount * (1 - variance), base_amount * (1 + variance)) + else: + return lambda: base_amount + + @staticmethod + def _cut_meshes(meshes: list, planes: list, fill_holes=False, remove_geom=True): + """ + Take the bmesh object and bisect it with all the planes given and remove the geometry outside of the planes + :param meshes: a list of the meshes to cut + :param planes: a list of tuples, each tuple being (plane position, plane normal). The normals should point + towards the center of the mesh, aka, geometry on the opposite side of the normal will be removed + """ + for mesh in meshes: + for plane in planes: + pos, normal = plane + geom = bmesh.ops.bisect_plane(mesh, geom=mesh.faces[:] + mesh.edges[:] + mesh.verts[:], dist=0.001, + plane_co=pos, plane_no=normal, clear_inner=remove_geom) + + if fill_holes: + JVBuilderBase._fill_holes(mesh, geom["geom_cut"]) + + mesh.faces.ensure_lookup_table() + mesh.edges.ensure_lookup_table() + mesh.verts.ensure_lookup_table() + + @staticmethod + def _fill_holes(mesh: bmesh.types.BMesh, cut_geometry): + """ + Given a mesh and geometry generated by using bisect_plane, fill the holes/ends + :param mesh: the mesh to operate + :param cut_geometry: a list of the new vertices, edges, and faces created by bisecting the mesh + """ + verts, edges = set(), set() + for item in cut_geometry: + if isinstance(item, bmesh.types.BMEdge): + edges.add(item) + verts.add(item.verts[0]) + verts.add(item.verts[1]) + + visited_verts = set() + grouped_edges = [] + + for v in verts: + if v not in visited_verts: + group = set() + JVBuilderBase._get_connected_edges(v, verts, visited_verts, edges, group) + grouped_edges.append(group) + + for group in grouped_edges: + bmesh.ops.edgenet_fill(mesh, edges=list(group)) + + @staticmethod + def _get_connected_edges(v, all_vs: set, visited_vs: set, edges: Union[dict, set], g: set): + """ + Starting at a given vertex 'v', follow all attached edges that are in 'edges' and collect them together into 'g' + The follow aspect is recursive, and the end result will be all connected edges being put in 'g' + :param v: The vertex to follow + :param all_vs: A set of all the vertices from the newly created geometry + :param visited_vs: The vertices that we have visited so far + :param edges: A set/dict of all the edges from the newly created geometry + :param g: The set of edges we are building that are connected + """ + visited_vs.add(v) + + for edge in v.link_edges: + if edge in edges: + g.add(edge) + + for vert in edge.verts: + if vert in all_vs and vert not in visited_vs: # if we have a vertex we haven't visited yet + JVBuilderBase._get_connected_edges(vert, all_vs, visited_vs, edges, g) + + @staticmethod + def _group_connected_faces(faces: set) -> List[set]: + """ + Take a set of faces and group them together based on whether the faces are connected, aka, share an edge + :param faces: a set of faces + :return: a list of sets of grouped faces + """ + groups = [] + visited = set() + for face in faces: + if face not in visited: + group = set() + JVBuilderBase._group_connected_faces_worker(face, faces, visited, group) + groups.append(group) + + return groups + + @staticmethod + def _group_connected_faces_worker(face: bmesh.types.BMFace, all_faces, visited_faces, group): + group.add(face) + visited_faces.add(face) + for edge in face.edges: + for linked_face in edge.link_faces: + if linked_face in all_faces and linked_face not in visited_faces: + JVBuilderBase._group_connected_faces_worker(linked_face, all_faces, visited_faces, group) + + @staticmethod + def _rotate_mesh_vertices(mesh, rotation): + for vert in mesh.verts: + vert.co.rotate(rotation) + + mesh.verts.ensure_lookup_table() + + @staticmethod + def _transform_vertex_positions(vertices, rotation=Euler((0, 0, 0)), before_translation=Vector((0, 0, 0)), + after_translation=Vector((0, 0, 0))): + for i in range(len(vertices)): + c = Vector(vertices[i]) + c += before_translation + c.rotate(rotation) + c += after_translation + vertices[i] = tuple(c) + + @staticmethod + def _add_material_index(faces, index: int): + for f in faces: + f.material_index = index + + @staticmethod + def _add_uv_seams_for_solidified_plane(extruded_geometry, original_edges, mesh): + """ + Add seams to all vertical edges and n-1 of the n top edges to allow the mesh to be unwrapped and lay flat. + To determine which top edges should be marked, first, all new vertices are collected, and then the edges + connecting them are grouped together based on whether they are connected or not. This groups new edges by + board, tile, etc. Next, the number of new faces connected to each edge is used to determine which edges to mark. + Only edges connected to one new face will be marked. Then mark all vertical edges + :param extruded_geometry: The new vertices, edges, and faces from bmesh.ops.solidify["geom"] + :param original_edges: the edges that formed the original plane + :param mesh: the current mesh object + """ + new_vertices = set() + new_faces = set() + new_edges = {} # maps new edge -> count of new faces that share it + for item in extruded_geometry: + if isinstance(item, bmesh.types.BMEdge): + new_edges[item] = 0 + elif isinstance(item, bmesh.types.BMVert): + new_vertices.add(item) + else: + new_faces.add(item) + + # determine how many new faces are connected to each edge + for face in new_faces: + for edge in face.edges: + new_edges[edge] += 1 + + # group edges by whether they are connected or not + visited_vertices = set() + grouped_edges = [] + for v in new_vertices: + if v not in visited_vertices: + group = set() + JVBuilderBase._get_connected_edges(v, new_vertices, visited_vertices, new_edges, group) + grouped_edges.append(group) + + # mark top edges + for group in grouped_edges: + first = True + for edge in group: + if new_edges[edge] == 1 and first: # skip one edge + first = False + continue + elif new_edges[edge] == 1: + edge.seam = True + + # mark vertical edges + og_edges = set(original_edges) + for edge in mesh.edges: + if edge not in og_edges and edge not in new_edges: + edge.seam = True + + @staticmethod + def _cutouts(mesh: bmesh.types.BMesh, props, object_matrix): + """ + For each added cutout, bisect the mesh according to the 6 faces of the cutout cubes. Then manually + remove all faces from the mesh that are contained within the cutout cubes. + :param mesh: the bmesh mesh + :param props: all JV properties + :param object_matrix: the matrix of the base object, needed for non-local cutouts + """ + mesh.normal_update() + inv_matrix = object_matrix.inverted() + _, inv_rot, _ = inv_matrix.decompose() + + for cutout in props.cutouts: + hx, hy, hz = Vector(cutout.dimensions) / 2 + center_normals = ( + ((hx, 0, 0), (-1, 0, 0)), + ((-hx, 0, 0), (1, 0, 0)), + ((0, +hy, 0), (0, -1, 0)), + ((0, -hy, 0), (0, 1, 0)), + ((0, 0, +hz), (0, 0, -1)), + ((0, 0, -hz), (0, 0, 1)) + ) + + # transform plane centers and normals + center_offset = Vector((hx, hy, hz)) + planes = [] + for c, n in center_normals: + p_center, p_normal = Vector(c), Vector(n) + + p_center.rotate(cutout.rotation) + p_normal.rotate(cutout.rotation) + p_center += cutout.location + center_offset + + if not cutout.local: + p_center = inv_matrix @ p_center # using new infix matrix multiplication + p_normal.rotate(inv_rot) + + planes.append((tuple(p_center), tuple(p_normal))) + + for plane_co, plane_normal in planes: + bmesh.ops.bisect_plane(mesh, geom=mesh.faces[:] + mesh.edges[:] + mesh.verts[:], dist=0.001, + plane_co=plane_co, plane_no=plane_normal) + + mesh.verts.ensure_lookup_table() + mesh.edges.ensure_lookup_table() + mesh.faces.ensure_lookup_table() + + # determine corner locations to know what geometry to remove + corners = [] + for lz in (-hz, hz): + for ly in (-hy, hy): + for lx in (-hx, hx): + corners.append(Vector((lx, ly, lz))) + + # transform cutout corners + for i in range(len(corners)): + corners[i].rotate(cutout.rotation) + corners[i] += cutout.location + + if not cutout.local: + corners[i] = inv_matrix @ corners[i] + + # find min and maxes of the corners to know the cutouts bounds + mins, maxs = list(corners[0]), list(corners[0]) + for corner in corners: + for i in range(3): + mins[i] = min(mins[i], corner[i]) + maxs[i] = max(maxs[i], corner[i]) + + cuboid = CuboidalRegion(planes) + + # remove faces + to_remove = [] + for face in mesh.faces: + c = face.calc_center_median() + if c in cuboid: + to_remove.append(face) + + for face in to_remove: + mesh.faces.remove(face) + + JVBuilderBase._clean_mesh(mesh) + + @staticmethod + def _clean_mesh(mesh: bmesh.types.BMesh): + """ + Remove all vertices and edges that aren't connected to anything + :param mesh: the mesh to clean + """ + to_remove = [] + for edge in mesh.edges: + if edge.is_wire: + to_remove.append(edge) + + for edge in to_remove: + mesh.edges.remove(edge) + + to_remove.clear() + for vertex in mesh.verts: + if vertex.is_wire: + to_remove.append(vertex) + + for vertex in to_remove: + mesh.verts.remove(vertex) + + mesh.verts.ensure_lookup_table() + mesh.edges.ensure_lookup_table() + mesh.faces.ensure_lookup_table() + + @classmethod + def _generate_mesh_from_converted_object(cls, props, context, rot_offset=(0, 0, 0), geometry_func_name="_geometry"): + """ + Since the object is converted, go through each face group, creating a new mesh, cutting it, + and then joining them all together into a mesh which is returned + :param cls: the architecture class to use for generating the geometry + :param props: JVProperties + :param context: the current context + :param rot_offset: a rotation offset for use with siding as it is built vertically not horizontally + :param geometry_func_name: the name of the method on the class that generates the geometry. The method + must be take in props and dimensions and return verts and faces + :return: the completed mesh + """ + objects = [] + main_obj = context.object + src = props.convert_source_object + + for fg in src.jv_properties.face_groups: # face groups on original object + verts, faces = getattr(cls, geometry_func_name)(props, tuple(fg.dimensions)) + rotated_verts = [] + + # rotate and shift vertices + rot = Euler([fg.rotation[i] + rot_offset[i] for i in range(3)]) + for v in verts: + vv = Vector(v) + vv.rotate(rot) + vv += fg.location + rotated_verts.append(tuple(vv)) + + mesh = bmesh.new() + cls._build_mesh_from_geometry(mesh, rotated_verts, faces) + + bpy.ops.mesh.primitive_cube_add() + new_obj = context.object + new_obj.location = src.location + objects.append(new_obj) + mesh.to_mesh(new_obj.data) + + if fg.is_convex: + mesh.normal_update() + + # cut mesh using bisect_plane for every edge, remove all geometry outside of planes + planes = [] + for plane in fg.bisecting_planes: + planes.append((tuple(plane.center), tuple(plane.normal))) + + cls._cut_meshes([mesh], planes) + mesh.to_mesh(new_obj.data) + else: + bpy.ops.object.modifier_add(type="BOOLEAN") + new_obj.modifiers["Boolean"].object = fg.boolean_object + bpy.ops.object.modifier_apply(apply_as='DATA', modifier="Boolean") + + mesh.free() + + # join objects + for obj in context.selected_objects: + obj.select_set(False) + for obj in objects: + obj.select_set(True) + context.view_layer.objects.active = objects[0] + + if len(objects) > 1: + bpy.ops.object.join() + + bm = bmesh.new() + bm.from_mesh(context.object.data) + cls._clean_mesh(bm) + + bpy.ops.object.delete() # remove object formed from joining meshes + + main_obj.select_set(True) + context.view_layer.objects.active = main_obj + + return bm + + @staticmethod + def _slope_top(props, meshes): + # clock-wise is positive for angles in mathutils + center = Vector((props.length / 2, 0, props.height)) + center += props.pitch_offset + angle = atan(props.pitch / 12) # angle of depression + + right_normal = Vector((1, 0, 0)) + right_normal.rotate(Euler((0, angle + radians(90), 0))) + left_normal = Vector((1, 0, 0)) + left_normal.rotate(Euler((0, radians(90) - angle, 0))) + + JVBuilderBase._cut_meshes(meshes, [ + (center, left_normal), + (center, right_normal) + ]) + + @staticmethod + def _mortar_geometry(props, dims: tuple): + # account for jointing + upper_x, upper_z = dims + th = props.thickness_thick * (1 - (props.grout_depth / 100)) + props.gap_uniform + lx = th if props.joint_left else 0 + rx = th if props.joint_right else 0 + + verts = [(-lx, 0, 0), (upper_x + rx, 0, 0), (upper_x + rx, 0, upper_z), (-lx, 0, upper_z)] + faces = [(0, 3, 2, 1)] + + return verts, faces + + @staticmethod + def _mirror(mesh, axis='X'): + """ + Duplicate and mirror existing geometry across the specified axis + :param mesh: the mesh to duplicate and mirror + :param axis: the axis to mirror across, must be in {'X', 'Y', 'Z'} + :return: + """ + # duplicate geometry + new_geom = bmesh.ops.duplicate(mesh, geom=mesh.verts[:] + mesh.edges[:] + mesh.faces[:])["geom"] + + i = {'X': 1, 'Y': 0, 'Z': 2}[axis.upper()] + for item in new_geom: + if isinstance(item, bmesh.types.BMVert): + item.co[i] *= -1 + + mesh.verts.ensure_lookup_table() + mesh.faces.ensure_lookup_table() diff --git a/jv_flooring.py b/jv_flooring.py index 6fd4645..03bcf84 100644 --- a/jv_flooring.py +++ b/jv_flooring.py @@ -1,1232 +1,645 @@ -# This file is part of JARCH Vis -# -# JARCH Vis is free software: you can redistribute it and/or modify +# This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # -# JARCH Vis is distributed in the hope that it will be useful, +# This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with JARCH Vis. If not, see . - -import bpy -from bpy.props import BoolProperty, EnumProperty, FloatProperty, StringProperty, IntProperty, FloatVectorProperty -from random import uniform -from mathutils import Vector -from math import tan, sin, cos, radians, sqrt -from . jv_materials import image_material, mortar_material -from . jv_utils import METRIC_FOOT, append_all, apply_modifier_boolean, unwrap_object, random_uvs -# import jv_properties - - -def create_flooring(mat, if_wood, if_tile, over_width, over_length, b_width, b_length, b_length2, is_length_vary, - length_vary, num_boards, space_l, space_w, spacing, t_width, t_length, is_offset, offset, - is_ran_offset, offset_vary, t_width2, is_width_vary, width_vary, max_boards, is_ran_thickness, - ran_thickness, th, hb_dir): - - verts = [] - faces = [] - - # create siding - if mat == "1": # Wood - if if_wood == "1": # Regular - verts, faces = wood_regular(over_width, over_length, b_width, b_length, space_l, space_w, is_length_vary, - length_vary, is_width_vary, width_vary, max_boards, is_ran_thickness, - ran_thickness, th) - elif if_wood == "2": # Parquet - verts, faces = wood_parquet(over_width, over_length, b_width, spacing, num_boards, th) - elif if_wood == "3": # Herringbone Parquet - verts, faces = wood_herringbone(over_width, over_length, b_width, b_length2, spacing, th, hb_dir, True) - elif if_wood == "4": # Herringbone - verts, faces = wood_herringbone(over_width, over_length, b_width, b_length2, spacing, th, hb_dir, False) - elif mat == "2": # Tile - if if_tile == "1": # Regular - verts, faces = tile_regular(over_width, over_length, t_width, t_length, spacing, is_offset, offset, - is_ran_offset, offset_vary, th) - elif if_tile == "2": # Large + Small - verts, faces = tile_ls(over_width, over_length, t_width, t_length, spacing, th) - elif if_tile == "3": # Large + Many Small - verts, faces = tile_lms(over_width, over_length, t_width, spacing, th) - elif if_tile == "4": # Hexagonal - verts, faces = tile_hexagon(over_width, over_length, t_width2, spacing, th) - - return verts, faces - - -def wood_herringbone(ow, ol, bw, bl, s, th, hb_dir, stepped): - verts = [] - faces = [] - cur_x, cur_y, cur_z = 0.0, 0.0, 0.0 - x_off, y_off = 0.0, 0.0 # used for finding farther forwards points when stepped - ang_s = s * sin(radians(45)) - s45 = s / sin(radians(45)) - - # step variables - if stepped: - x_off = cos(radians(45)) * bw - y_off = sin(radians(45)) * bw - - wid_off = sin(radians(45)) * bl # offset from one end of the board to the other inline with width - len_off = cos(radians(45)) * bl # offset from one end of the board to the other inline with length - w = bw / cos(radians(45)) # width adjusted for 45 degree rotation - - # figure out starting position - if hb_dir == "1": - cur_y = -wid_off - - elif hb_dir == "2": - cur_x = ow - cur_y = ol + wid_off - - elif hb_dir == "3": - cur_y = ol - cur_x = -wid_off - - elif hb_dir == "4": - cur_x = ow + wid_off - - # loop going forwards - while (hb_dir == "1" and cur_y < ol + wid_off) or (hb_dir == "2" and cur_y > 0 - wid_off) or \ - (hb_dir == "3" and cur_x < ow + wid_off) or (hb_dir == "4" and cur_x > 0 - wid_off): - going_forwards = True - - # loop going right - while (hb_dir == "1" and cur_x < ow) or (hb_dir == "2" and cur_x > 0) or (hb_dir == "3" and cur_y > 0) or \ - (hb_dir == "4" and cur_y < ol): - p = len(verts) - - # add verts - # forwards - if hb_dir == "1": - verts += [(cur_x, cur_y, cur_z), (cur_x, cur_y, cur_z + th)] - - if stepped and cur_x != 0: - verts += [(cur_x - x_off, cur_y + y_off, cur_z), (cur_x - x_off, cur_y + y_off, cur_z + th)] - else: - verts += [(cur_x, cur_y + w, cur_z), (cur_x, cur_y + w, cur_z + th)] - - if going_forwards: - cur_y += wid_off - else: - cur_y -= wid_off - cur_x += len_off - - verts += [(cur_x, cur_y, cur_z), (cur_x, cur_y, cur_z + th)] - if stepped: - verts += [(cur_x - x_off, cur_y + y_off, cur_z), (cur_x - x_off, cur_y + y_off, cur_z + th)] - cur_x -= x_off - ang_s - if going_forwards: - cur_y += y_off + ang_s - else: - cur_y -= y_off + ang_s - else: - verts += [(cur_x, cur_y + w, cur_z), (cur_x, cur_y + w, cur_z + th)] - cur_x += s - - # backwards - elif hb_dir == "2": - verts += [(cur_x, cur_y, cur_z), (cur_x, cur_y, cur_z + th)] - - if stepped and cur_x != ow: - verts += [(cur_x + x_off, cur_y - y_off, cur_z), (cur_x + x_off, cur_y - y_off, cur_z + th)] - else: - verts += [(cur_x, cur_y - w, cur_z), (cur_x, cur_y - w, cur_z + th)] - - if going_forwards: - cur_y -= wid_off - else: - cur_y += wid_off - cur_x -= len_off - - verts += [(cur_x, cur_y, cur_z), (cur_x, cur_y, cur_z + th)] - if stepped: - verts += [(cur_x + x_off, cur_y - y_off, cur_z), (cur_x + x_off, cur_y - y_off, cur_z + th)] - cur_x += x_off - ang_s - if going_forwards: - cur_y -= y_off + ang_s - else: - cur_y += y_off + ang_s - else: - verts += [(cur_x, cur_y - w, cur_z), (cur_x, cur_y - w, cur_z + th)] - cur_x -= s - # right - elif hb_dir == "3": - verts += [(cur_x, cur_y, cur_z), (cur_x, cur_y, cur_z + th)] - - if stepped and cur_y != ol: - verts += [(cur_x + y_off, cur_y + x_off, cur_z), (cur_x + y_off, cur_y + x_off, cur_z + th)] - else: - verts += [(cur_x + w, cur_y, cur_z), (cur_x + w, cur_y, cur_z + th)] - - if going_forwards: - cur_x += wid_off - else: - cur_x -= wid_off - cur_y -= len_off - - verts += [(cur_x, cur_y, cur_z), (cur_x, cur_y, cur_z + th)] - if stepped: - verts += [(cur_x + y_off, cur_y + x_off, cur_z), (cur_x + y_off, cur_y + x_off, cur_z + th)] - cur_y += x_off - ang_s - if going_forwards: - cur_x += y_off + ang_s - else: - cur_x -= y_off + ang_s - else: - verts += [(cur_x + w, cur_y, cur_z), (cur_x + w, cur_y, cur_z + th)] - cur_y -= s - # left - else: - verts += [(cur_x, cur_y, cur_z), (cur_x, cur_y, cur_z + th)] - - if stepped and cur_y != 0: - verts += [(cur_x - y_off, cur_y - x_off, cur_z), (cur_x - y_off, cur_y - x_off, cur_z + th)] - else: - verts += [(cur_x - w, cur_y, cur_z), (cur_x - w, cur_y, cur_z + th)] - - if going_forwards: - cur_x -= wid_off - else: - cur_x += wid_off - cur_y += len_off - - verts += [(cur_x, cur_y, cur_z), (cur_x, cur_y, cur_z + th)] - if stepped: - verts += [(cur_x - y_off, cur_y - x_off, cur_z), (cur_x - y_off, cur_y - x_off, cur_z + th)] - cur_y -= x_off - ang_s - if going_forwards: - cur_x -= y_off + ang_s - else: - cur_x += y_off + ang_s - else: - verts += [(cur_x - w, cur_y, cur_z), (cur_x - w, cur_y, cur_z + th)] - cur_y += s - - # faces - faces += [(p, p + 1, p + 3, p + 2), (p, p + 4, p + 5, p + 1), (p + 4, p + 6, p + 7, p + 5), - (p + 6, p + 2, p + 3, p + 7), (p + 1, p + 5, p + 7, p + 3), (p, p + 2, p + 6, p + 4)] - - # flip going_right - going_forwards = not going_forwards - x_off *= -1 - - # if not in forwards position, then move back before adjusting values for next row - if not going_forwards: - x_off = abs(x_off) - if hb_dir == "1": - cur_y -= wid_off - if stepped: - cur_y -= y_off + ang_s - elif hb_dir == "2": - cur_y += wid_off - if stepped: - cur_y += y_off + ang_s - elif hb_dir == "3": - cur_x -= wid_off - if stepped: - cur_x -= y_off + ang_s +# along with this program. If not, see . + +from . jv_builder_base import JVBuilderBase +from math import sqrt, cos, tan, radians + + +class JVFlooring(JVBuilderBase): + is_cutable = True + is_convertible = True + + @staticmethod + def draw(props, layout): + layout.prop(props, "flooring_pattern", icon="MESH_GRID") + + layout.separator() + row = layout.row() + row.prop(props, "width") + row.prop(props, "length") + + layout.separator() + row = layout.row() + + # length and width - wood-like + if props.flooring_pattern == "regular": + row.prop(props, "board_width_medium") + row.prop(props, "board_length_medium") + elif props.flooring_pattern in ("herringbone", "chevron"): + row.prop(props, "board_width_narrow") + row.prop(props, "board_length_short") + elif props.flooring_pattern == "checkerboard": + row.prop(props, "checkerboard_board_count") + row.prop(props, "board_length_really_short") + # tile-like + elif props.flooring_pattern == "windmill": + row.prop(props, "tile_width") + elif props.flooring_pattern in ("hexagons", "octagons"): + row.prop(props, "side_length") + else: # hopscotch, stepping_stone, corridor + row.prop(props, "tile_width") + row.prop(props, "tile_length") + + if props.flooring_pattern == "corridor": + layout.prop(props, "alternating_row_width") + + if props.flooring_pattern == "hexagons": + layout.prop(props, "with_dots", icon="ACTION") + + # length and width variance + if props.flooring_pattern == "regular": + layout.separator() + row = layout.row() + + row.prop(props, "vary_width", icon="RNDCURVE") + if props.vary_width: + row.prop(props, "width_variance") + + layout.separator() + row = layout.row() + + row.prop(props, "vary_length", icon="RNDCURVE") + if props.vary_length: + row.prop(props, "length_variance") + + if props.flooring_pattern in ("regular", "corridor"): + # row offset + layout.separator() + row = layout.row() + + row.prop(props, "vary_row_offset", icon="RNDCURVE") + if props.vary_row_offset: + row.prop(props, "row_offset_variance") else: - cur_x += wid_off - if stepped: - cur_x += y_off + ang_s - - # adjust forwards - if hb_dir == "1": - cur_y += w + s45 - cur_x = 0 - elif hb_dir == "2": - cur_y -= w + s45 - cur_x = ow - elif hb_dir == "3": - cur_x += w + s45 - cur_y = ol + row.prop(props, "row_offset") + + # thickness and variance + layout.separator() + box = layout.box() + box.prop(props, "thickness") + + row = box.row() + row.prop(props, "vary_thickness", icon="RNDCURVE") + if props.vary_thickness: + row.prop(props, "thickness_variance") + + # gaps + layout.separator() + row = layout.row() + row.prop(props, "gap_uniform") + + @staticmethod + def update(props, context): + if props.convert_source_object is not None: # then this is a converted object + mesh = JVFlooring._generate_mesh_from_converted_object(props, context) else: - cur_x -= w + s45 - cur_y = 0 - - return verts, faces - - -# tile large + small -def tile_ls(ow, ol, tw, tl, s, th): - verts = [] - faces = [] - hw = (tw / 2) - (s / 2) - hl = (tl / 2) - (s / 2) - cur_y = tl + hl + s + s - z = th - - while cur_y < ol: - cur_x = -hw - s - st_y = float(cur_y) - - while cur_x < ow: - # large - for i in range(5): - p = len(verts) - if cur_x < ow and cur_y < ol: - append_all(verts, [(cur_x, cur_y + tl, 0.0), (cur_x, cur_y + tl, z), (cur_x, cur_y, z), - (cur_x, cur_y, 0.0)]) - cur_x += tw - append_all(verts, [(cur_x, cur_y + tl, 0.0), (cur_x, cur_y + tl, z), (cur_x, cur_y, z), - (cur_x, cur_y, 0.0)]) - - append_all(faces, [(p, p + 3, p + 2, p + 1), (p + 4, p + 5, p + 6, p + 7), (p, p + 4, p + 7, p + 3), - (p + 3, p + 7, p + 6, p + 2), (p + 1, p + 2, p + 6, p + 5), - (p, p + 1, p + 5, p + 4)]) - - p = len(verts) - - if i == 0: - x = cur_x + s - y = cur_y + s + hl - elif i == 1: - x = cur_x - tw - y = cur_y + tl + s - elif i == 2: - x = cur_x + s - y = cur_y + hl + s - elif i == 3: - x = cur_x - tw - y = cur_y + tl + s - else: - x = cur_x - hw - y = cur_y - s - hl - - append_all(verts, [(x, y+hl, 0.0), (x, y + hl, z), (x, y, z), (x, y, 0.0)]) - x += hw - append_all(verts, [(x, y+hl, 0.0), (x, y + hl, z), (x, y, z), (x, y, 0.0)]) - - append_all(faces, [(p, p + 3, p + 2, p + 1), (p + 4, p + 5, p + 6, p + 7), (p, p + 4, p + 7, p + 3), - (p + 3, p + 7, p + 6, p + 2), (p + 1, p + 2, p + 6, p + 5), - (p, p + 1, p + 5, p + 4)]) - - # update position - if i == 0: - cur_x -= hw - cur_y -= s + hl + s + tl - elif i == 1: - cur_x -= hw - cur_y += tl + s - elif i == 2: - cur_x -= hw - cur_y -= tl + s + hl + s - elif i == 3: - cur_x -= hw - cur_y += tl + s - elif i == 4: - cur_x -= hw - cur_y += tl + s + mesh = JVFlooring._start(context) + verts, faces = JVFlooring._geometry(props, (props.length, props.width)) + JVFlooring._build_mesh_from_geometry(mesh, verts, faces) + + # cut if needed + if props.flooring_pattern in ("herringbone", "chevron", "hopscotch", "stepping_stone", "hexagons", + "octagons", "windmill"): + JVFlooring._cut_meshes([mesh], [ + ((0, 0, 0), (1, 0, 0)), # left + ((0, 0, 0), (0, 1, 0)), # bottom + ((props.length, 0, 0), (-1, 0, 0)), # right + ((0, props.width, 0), (0, -1, 0)) # top + ]) + + if props.add_cutouts: + JVFlooring._cutouts(mesh, props, context.object.matrix_world) + + original_edges = mesh.edges[:] # used to determine where seams should be added + + # solidify + new_geometry = JVFlooring._solidify(mesh, + JVFlooring._create_variance_function(props.vary_thickness, + props.thickness, + props.thickness_variance)) + + # main material index + JVFlooring._add_material_index(mesh.faces, 0) + + # add uv seams + JVFlooring._add_uv_seams_for_solidified_plane(new_geometry, original_edges, mesh) + + JVFlooring._finish(context, mesh) + JVFlooring._uv_unwrap() + + @staticmethod + def _geometry(props, dims: tuple): + verts, faces = [], [] + + # dynamically call correct method as their names will match up with the style name + getattr(JVFlooring, "_{}".format(props.flooring_pattern))(dims, props, verts, faces) + + return verts, faces + + @staticmethod + def _regular(dims: tuple, props, verts, faces): + width_variance = JVFlooring._create_variance_function(props.vary_width, props.board_width_medium, + props.width_variance) + length_variance = JVFlooring._create_variance_function(props.vary_length, props.board_length_medium, + props.length_variance) + + first_length_for_fixed_offset = props.board_length_medium * (props.row_offset / 100) + if first_length_for_fixed_offset == 0: + first_length_for_fixed_offset = props.board_length_medium + + offset_length_variance = JVFlooring._create_variance_function(props.vary_row_offset, + props.board_length_medium / 2, + props.row_offset_variance) + + y = 0 + odd = False + upper_x, upper_y = dims + while y < upper_y: + x = 0 + + width = width_variance() + while x < upper_x: + length = length_variance() + if x == 0: # first board + if props.vary_row_offset: + length = offset_length_variance() + elif odd: + length = first_length_for_fixed_offset + + trimmed_width = min(width, upper_y-y) + trimmed_length = min(length, upper_x-x) + + verts += [ + (x, y, 0), + (x+trimmed_length, y, 0), + (x+trimmed_length, y+trimmed_width, 0), + (x, y+trimmed_width, 0) + ] + + p = len(verts) - 4 + faces.append((p, p+3, p+2, p+1)) + + x += length + props.gap_uniform + + y += width + props.gap_uniform + odd = not odd + + @staticmethod + def _checkerboard(dims: tuple, props, verts, faces): + length = props.board_length_really_short + board_count = props.checkerboard_board_count + gap = props.gap_uniform + + # the width of each board so that the total of them is the same as the length + width = (length - (gap * (board_count - 1))) / board_count + + y = 0 + upper_x, upper_y = dims + start_vertical = False + while y < upper_y: + x = 0 + + vertical = start_vertical + while x < upper_x: + if vertical: + for _ in range(board_count): + if x < upper_x: + # width is paired with x and length with y because board is rotated + trimmed_width = min(width, upper_x-x) + trimmed_length = min(length, upper_y-y) + + verts += [ + (x, y, 0), + (x+trimmed_width, y, 0), + (x+trimmed_width, y+trimmed_length, 0), + (x, y+trimmed_length, 0) + ] + + p = len(verts) - 4 + faces.append((p, p+3, p+2, p+1)) + + x += width + gap else: - cur_x += hw - cur_y = st_y + tl + s + tl + s + s + hl - - return verts, faces - - -def tile_hexagon(ow, ol, tw, s, th): - verts = [] - faces = [] - offset = False - z = th - w = tw / 2 - cur_y = 0.0 - h = w * tan(radians(30)) - r = sqrt((w * w) + (h * h)) - - while cur_y < ol + tw: - if not offset: - cur_x = 0.0 - else: - cur_x = w + (s / 2) - - while cur_x < ow: - p = len(verts) - append_all(verts, [(cur_x + w, cur_y + h, 0.0), (cur_x, cur_y + r, 0.0), (cur_x - w, cur_y + h, 0.0), - (cur_x - w, cur_y - h, 0.0), (cur_x, cur_y - r, 0.0), (cur_x + w, cur_y - h, 0.0)]) - append_all(verts, [(cur_x + w, cur_y + h, z), (cur_x, cur_y + r, z), (cur_x - w, cur_y + h, z), - (cur_x - w, cur_y - h, z), (cur_x, cur_y - r, z), (cur_x + w, cur_y - h, z)]) - - n = p - for i in range(5): - faces.append((n + i, n + i + 1, n + i + 7, n + i + 6)) - - append_all(faces, [(p + 5, p, p + 6, p + 11), (p + 3, p + 2, p + 1, p), (p + 5, p + 4, p + 3, p), - (p + 6, p + 7, p + 8, p + 9), (p + 9, p + 10, p + 11, p + 6)]) - - cur_x += tw + s - - cur_y += r + h + s - offset = not offset - - return verts, faces - - -# tile large + many small -def tile_lms(ow, ol, tw, s, th): - verts = [] - faces = [] - small = True - z = th - cur_y = 0.0 - ref = (tw - s) / 2 - - tw2, tl2, tw3, tl3 = ref, ref, tw, tw - - while cur_y < ol: - cur_x = 0.0 - large = False - while cur_x < ow: - if small: - if cur_x < ow < cur_x + ref: - tw2 = ow - cur_x - if cur_y < ol < cur_y + ref: - tl2 = ol - cur_y + ty = y + for _ in range(board_count): + if ty < upper_y: + # width is paired with x and length with y because board is rotated + trimmed_width = min(width, upper_y - ty) + trimmed_length = min(length, upper_x - x) + + verts += [ + (x, ty, 0), + (x + trimmed_length, ty, 0), + (x + trimmed_length, ty + trimmed_width, 0), + (x, ty + trimmed_width, 0) + ] + + p = len(verts) - 4 + faces.append((p, p+3, p+2, p+1)) + + ty += width + gap + x += length + gap + + vertical = not vertical + + y += length + gap + start_vertical = not start_vertical + + @staticmethod + def _herringbone(dims: tuple, props, verts, faces): + length = props.board_length_short + width = props.board_width_narrow + + # boards oriented at 45 degree angle + leg_length = length / sqrt(2) + leg_width = width / sqrt(2) + leg_gap = props.gap_uniform / sqrt(2) + long_leg_gap = props.gap_uniform * sqrt(2) + + start_y = -leg_length # start down some so there are no gaps + upper_x, upper_y = dims + while start_y < upper_y + leg_width: # go a little further than need to ensure no gaps + x = 0 + + y = start_y + while x < upper_x: + # board width positive slope - starting from bottom-most corner + verts += [ + (x, y, 0), + (x+leg_length, y+leg_length, 0), + (x+leg_length-leg_width, y+leg_length+leg_width, 0), + (x-leg_width, y+leg_width, 0) + ] + + p = len(verts) - 4 + faces.append((p, p+3, p+2, p+1)) + + # move to left-most corner of complementary board + x += leg_length + leg_gap - leg_width + y += leg_length - leg_gap - leg_width + + # board with negative slope - starting from left-most corner + verts += [ + (x, y, 0), + (x + leg_length, y - leg_length, 0), + (x + leg_length + leg_width, y - leg_length + leg_width, 0), + (x + leg_width, y + leg_width, 0) + ] + + p = len(verts) - 4 + faces.append((p, p+3, p+2, p+1)) + + # move to bottom-most corner of next board + x += leg_length + leg_width + leg_gap + y += -leg_length + leg_width + leg_gap + + start_y += 2 * leg_width + long_leg_gap + + @staticmethod + def _chevron(dims: tuple, props, verts, faces): + leg_length = props.board_length_short / sqrt(2) + leg_width = props.board_width_narrow * sqrt(2) + leg_gap = props.gap_uniform * sqrt(2) + + start_y = -leg_length + upper_x, upper_y = dims + while start_y < upper_y: + x = 0 + y = start_y + + y_leg_length = leg_length + while x < upper_x: + verts += [ + (x, y, 0), + (x+leg_length, y+y_leg_length, 0), + (x+leg_length, y+y_leg_length+leg_width, 0), + (x, y+leg_width, 0) + ] + + p = len(verts) - 4 + faces.append((p, p+3, p+2, p+1)) + + x += leg_length + props.gap_uniform + y += y_leg_length + + y_leg_length *= -1 # next board will have opposite slope + + start_y += leg_width + leg_gap + + @staticmethod + def _hopscotch(dims: tuple, props, verts, faces): + """ + A row consists of every group that is at the same y value. A row is built, then the x offset for the next + row is determined and that row is created + """ + length, width, gap = props.tile_length, props.tile_width, props.gap_uniform + half_length, half_width = (length - gap) / 2, (width - gap) / 2 + + # the actual distance += half_length + gap, but x is moved that much between creating the two tiles + distance_between_groups = (2 * gap) + (2 * length) + + x_start_values = [ # there are 5 different rows, each with a different starting x value + -gap - length - gap - half_length, # orange + 0, # pink + -(length / 2) - gap - half_length - (gap / 2), # yellow + -(2 * gap) - (2*length), # green + -gap - half_length # blue + ] + + row = 0 + y = -gap - half_width + upper_x, upper_y = dims + while y < upper_y: + x = x_start_values[row % len(x_start_values)] + + while x < upper_x: + verts += [ # small tile + (x, y, 0), + (x+half_length, y, 0), + (x+half_length, y+half_width, 0), + (x, y+half_width, 0) + ] + + x += half_length + gap + + verts += [ # large tile + (x, y, 0), + (x + length, y, 0), + (x + length, y + width, 0), + (x, y + width, 0) + ] + + p = len(verts) - 8 + faces.extend(((p, p+3, p+2, p+1), (p+4, p+7, p+6, p+5))) + + x += distance_between_groups + + # we've finished the row, make sure we start in the right place next time + y += half_width + gap + row += 1 + + @staticmethod + def _windmill(dims: tuple, props, verts, faces): + length = props.tile_width + gap = props.gap_uniform + width = (length - gap) / 2 + + y = 0 + upper_x, upper_y = dims + while y < upper_y: + x = 0 + while x < upper_x: + verts += [ + (x, y, 0), # bottom - horizontal + (x+length, y, 0), + (x+length, y+width, 0), + (x, y+width, 0), + + (x, y+width+gap, 0), # left - vertical + (x+width, y+width+gap, 0), + (x+width, y+width+gap+length, 0), + (x, y+width+gap+length, 0), + + (x+width+gap, y+length+gap, 0), # top - horizontal + (x+length+gap+width, y+length+gap, 0), + (x+length+gap+width, y+length+gap+width, 0), + (x+width+gap, y+length+gap+width, 0), + + (x+length+gap, y, 0), # right - vertical + (x+length+gap+width, y, 0), + (x+length+gap+width, y+length, 0), + (x+length+gap, y+length, 0), + + (x+width+gap, y+width+gap, 0), # center + (x+length, y+width+gap, 0), + (x+length, y+length, 0), + (x+width+gap, y+length, 0) + ] p = len(verts) - append_all(verts, [(cur_x, cur_y + tl2, 0.0), (cur_x, cur_y + tl2, z), (cur_x, cur_y, z), - (cur_x, cur_y, 0.0)]) - cur_x += tw2 - append_all(verts, [(cur_x, cur_y + tl2, 0.0), (cur_x, cur_y + tl2, z), (cur_x, cur_y, z), - (cur_x, cur_y, 0.0)]) - - append_all(faces, [(p, p + 3, p + 2, p + 1), (p + 4, p + 5, p + 6, p + 7), (p, p + 4, p + 7, p + 3), - (p + 3, p + 7, p + 6, p + 2), (p + 1, p + 2, p + 6, p + 5), (p, p + 1, p + 5, p+4)]) - else: - if not large: - error = [] - for i in range(2): - if cur_x < ow and cur_y < ol: - if cur_x + ref > ow: - tw2 = ow - cur_x - if cur_y + ref > ol: - tl2 = ol - cur_y - p = len(verts) - append_all(verts, [(cur_x, cur_y + tl2, 0.0), (cur_x, cur_y + tl2, z), (cur_x, cur_y, z), - (cur_x, cur_y, 0.0)]) - cur_x += tw2 - append_all(verts, [(cur_x, cur_y + tl2, 0.0), (cur_x, cur_y + tl2, z), (cur_x, cur_y, z), - (cur_x, cur_y, 0.0)]) - cur_y += tl2 + s - cur_x -= tw2 - error.append(tl2) - - append_all(faces, [(p, p + 3, p + 2, p + 1), (p + 4, p + 5, p + 6, p + 7), - (p, p + 4, p + 7, p + 3), (p + 3, p + 7, p + 6, p + 2), - (p + 1, p + 2, p + 6, p + 5), (p, p + 1, p + 5, p + 4)]) - for i in error: - cur_y -= i + s - cur_x += tw2 + for i in range(p-20, p, 4): + faces.append((i, i+3, i+2, i+1)) + + x += length + gap + width + gap + y += length + gap + width + gap + + @staticmethod + def _stepping_stone(dims: tuple, props, verts, faces): + length, width, gap = props.tile_length, props.tile_width, props.gap_uniform + half_length, half_width = (length - gap) / 2, (width - gap) / 2 + + y = 0 + upper_x, upper_y = dims + while y < upper_y: + x = 0 + while x < upper_x: + tx = x + ty = y + for _ in range(3): # three tiles along the bottom + verts += [ + (x, y, 0), + (x+half_length, y, 0), + (x+half_length, y+half_width, 0), + (x, y+half_width, 0) + ] + + x += half_length + gap + + x = tx + y += half_width + gap + + verts += [ + (x, y, 0), + (x+length, y, 0), + (x+length, y+width, 0), + (x, y+width, 0) + ] + + x += length + gap + + for _ in range(2): + verts += [ + (x, y, 0), + (x + half_length, y, 0), + (x + half_length, y + half_width, 0), + (x, y + half_width, 0) + ] + + y += half_width + gap + p = len(verts) + for i in range(p-24, p, 4): # 6 faces, 4 vertices each + faces.append((i, i+3, i+2, i+1)) + + x += half_length + gap + y = ty + + y += half_width + width + (2*gap) + + @staticmethod + def _hexagons(dims: tuple, props, verts, faces): + side_length, gap = props.side_length, props.gap_uniform + x_leg = side_length / 2 + y_leg = x_leg / tan(radians(30)) + d = y_leg / cos(radians(30)) # distance from center of hexagon to each vertex + gap_dif = (gap / 2) * sqrt(3) + + # if we are doing dots, figure out the difference between the center and points, actual size is 2x values + dot_x = d + (gap/2) - x_leg - gap_dif + dot_y = ((2*y_leg) + gap - (2*gap_dif)) / 2 + + start_y = y_leg + upper_x, upper_y = dims[0] + d, dims[1] + (2*y_leg) + while start_y < upper_y: + move_down = True + x = x_leg + + y = start_y + while x < upper_x: + verts += [ + (x-x_leg, y-y_leg, 0), + (x+x_leg, y-y_leg, 0), + (x+d, y, 0), + (x+x_leg, y+y_leg, 0), + (x-x_leg, y+y_leg, 0), + (x-d, y, 0) + ] + + p = len(verts) - 6 + faces.append((p, p+5, p+4, p+3, p+2, p+1)) + + if props.with_dots: + # add cube dot + x += d + (gap/2) + y -= gap_dif + + verts += [ + (x, y, 0), + (x-dot_x, y-dot_y, 0), + (x, y-dot_y-dot_y, 0), + (x+dot_x, y-dot_y, 0), + ] + + y += gap_dif + x += d + (gap / 2) + + p = len(verts) - 4 + faces.append((p, p+3, p+2, p+1)) else: - if cur_x + tw > ow: - tw3 = ow - cur_x - if cur_y + tw > ol: - tl3 = ol - cur_y - p = len(verts) - append_all(verts, [(cur_x, cur_y + tl3, 0.0), (cur_x, cur_y + tl3, z), (cur_x, cur_y, z), - (cur_x, cur_y, 0.0)]) - cur_x += tw3 - append_all(verts, [(cur_x, cur_y + tl3, 0.0), (cur_x, cur_y + tl3, z), (cur_x, cur_y, z), - (cur_x, cur_y, 0.0)]) - - append_all(faces, [(p, p + 3, p + 2, p + 1), (p + 4, p + 5, p + 6, p + 7), (p, p + 4, p + 7, p + 3), - (p + 3, p + 7, p + 6, p + 2), (p + 1, p + 2, p + 6, p + 5), (p, p+1, p+5, p+4)]) - - large = not large - cur_x += s - if small: - cur_y += tl2 + s - else: - cur_y += tl3 + s - small = not small - - return verts, faces - - -def tile_regular(ow, ol, tw, tl, s, is_offset, offset, is_ran_offset, offset_vary, th): - verts = [] - faces = [] - off = False - o = 1 / (100 / offset) - cur_y = 0.0 - z = th - - while cur_y < ol: - cur_x = 0.0 - tl2 = tl - if cur_y < ol < cur_y + tl: - tl2 = ol - cur_y - - while cur_x < ow: - p = len(verts) - tw2 = tw - if cur_x < ow < cur_x + tw: - tw2 = ow - cur_x - elif cur_x == 0.0 and off and is_offset and not is_ran_offset: - tw2 = tw * o - elif cur_x == 0.0 and is_offset and is_ran_offset: - v = tl * 0.0049 * offset_vary - tw2 = uniform((tl / 2) - v, (tl / 2) + v) - - append_all(verts, [(cur_x, cur_y+tl2, 0.0), (cur_x, cur_y+tl2, z), (cur_x, cur_y, z), (cur_x, cur_y, 0.0)]) - cur_x += tw2 - append_all(verts, [(cur_x, cur_y+tl2, 0.0), (cur_x, cur_y+tl2, z), (cur_x, cur_y, z), (cur_x, cur_y, 0.0)]) - cur_x += s - - append_all(faces, [(p, p + 3, p + 2, p + 1), (p + 4, p + 5, p + 6, p + 7), (p, p + 4, p + 7, p + 3), - (p + 3, p + 7, p + 6, p + 2), (p + 1, p + 2, p + 6, p + 5), (p, p + 1, p + 5, p + 4)]) - - cur_y += tl2 + s - off = not off - - return verts, faces - - -def wood_parquet(ow, ol, bw, s, num_boards, th): - verts = [] - faces = [] - cur_x = 0.0 - z = th - start_orient_length = True - - # figure board length - bl = (bw * num_boards) + (s * (num_boards - 1)) - while cur_x < ow: - cur_y = 0.0 - orient_length = start_orient_length - while cur_y < ol: - bl2 = bl - bw2 = bw - - if orient_length: - start_x = cur_x - - for i in range(num_boards): - if cur_x < ow and cur_y < ol: - # make sure board should be placed - if cur_x < ow < cur_x + bw: - bw2 = ow - cur_x - if cur_y < ol < cur_y + bl: - bl2 = ol - cur_y - p = len(verts) - append_all(verts, [(cur_x, cur_y, 0.0), (cur_x, cur_y, z), (cur_x + bw2, cur_y, z), - (cur_x + bw2, cur_y, 0.0)]) - cur_y += bl2 - append_all(verts, [(cur_x, cur_y, 0.0), (cur_x, cur_y, z), (cur_x + bw2, cur_y, z), - (cur_x + bw2, cur_y, 0.0)]) - cur_y -= bl2 - cur_x += bw2 + s - - append_all(faces, [(p, p + 3, p + 2, p + 1), (p + 4, p + 5, p + 6, p + 7), - (p, p + 4, p + 7, p + 3), (p + 3, p + 7, p + 6, p + 2), - (p + 1, p + 2, p + 6, p + 5), (p, p + 1, p + 5, p + 4)]) - - cur_x = start_x - cur_y += bl2 + s - - else: - for i in range(num_boards): - if cur_x < ow and cur_y < ol: - if cur_x < ow < cur_x + bl: - bl2 = ow - cur_x - if cur_y < ol < cur_y + bw: - bw2 = ol - cur_y - p = len(verts) - append_all(verts, [(cur_x, cur_y + bw2, 0.0), (cur_x, cur_y + bw2, z), (cur_x, cur_y, z), - (cur_x, cur_y, 0.0)]) - cur_x += bl2 - append_all(verts, [(cur_x, cur_y + bw2, 0.0), (cur_x, cur_y + bw2, z), (cur_x, cur_y, z), - (cur_x, cur_y, 0.0)]) - cur_x -= bl2 - cur_y += bw2 + s - - append_all(faces, [(p, p + 3, p + 2, p + 1), (p + 4, p + 5, p + 6, p + 7), - (p, p + 4, p + 7, p + 3), (p + 3, p + 7, p + 6, p + 2), - (p + 1, p + 2, p + 6, p + 5), (p, p + 1, p + 5, p + 4)]) - - orient_length = not orient_length - - start_orient_length = not start_orient_length - cur_x += bl + s - - return verts, faces - - -def wood_regular(ow, ol, bw, bl, s_l, s_w, is_length_vary, length_vary, is_width_vary, width_vary, max_boards, is_r_h, - r_h, th): - verts = [] - faces = [] - cur_x = 0.0 - zt = th - - while cur_x < ow: - if is_width_vary: - v = bw * (width_vary / 100) * 0.99 - bw2 = uniform(bw - v, bw + v) - else: - bw2 = bw - - if bw2 + cur_x > ow: - bw2 = ow - cur_x - cur_y = 0.0 - counter = 1 - - while cur_y < ol: - z = zt - if is_r_h: - v = z * 0.99 * (r_h / 100) - z = uniform(z - v, z + v) - bl2 = bl - if is_length_vary: - v = bl * (length_vary / 100) * 0.99 - bl2 = uniform(bl - v, bl + v) - if (counter >= max_boards and is_length_vary) or cur_y + bl2 > ol: - bl2 = ol - cur_y - p = len(verts) - - append_all(verts, [(cur_x, cur_y, 0.0), (cur_x, cur_y, z), (cur_x+bw2, cur_y, z), (cur_x+bw2, cur_y, 0.0)]) - cur_y += bl2 - append_all(verts, [(cur_x, cur_y, 0.0), (cur_x, cur_y, z), (cur_x+bw2, cur_y, z), (cur_x+bw2, cur_y, 0.0)]) - cur_y += s_l - - append_all(faces, [(p, p + 3, p + 2, p + 1), (p, p + 4, p + 7, p + 3), (p + 3, p + 7, p + 6, p + 2), - (p + 1, p + 2, p + 6, p + 5), (p, p + 1, p + 5, p + 4), (p + 4, p + 5, p + 6, p + 7)]) - counter += 1 - - cur_x += bw2 + s_w - - return verts, faces - - -def tile_grout(ow, ol, depth, th): - verts = [] - faces = [] - z = th - depth - x = ow - y = ol - - append_all(verts, [(0.0, 0.0, 0.0), (0.0, 0.0, z), (x, 0.0, z), (x, 0.0, 0.0), (0.0, y, 0.0), (0.0, y, z), - (x, y, z), (x, y, 0.0)]) - - append_all(faces, [(0, 3, 2, 1), (4, 5, 6, 7), (0, 1, 5, 4), (1, 2, 6, 5), (3, 7, 6, 2), (0, 4, 7, 3)]) - - return verts, faces - - -def tile_cutter(ow, ol): - verts = [] - faces = [] - z = 0.5 - x = ow - y = ol - - append_all(verts, [(0.0, 0.0, -0.5), (0.0, 0.0, z), (x, 0.0, z), (x, 0.0, -0.5), (0.0, y, -0.5), (0.0, y, z), - (x, y, z), (x, y, -0.5)]) - - append_all(faces, [(0, 3, 2, 1), (4, 5, 6, 7), (0, 1, 5, 4), (1, 2, 6, 5), (3, 7, 6, 2), (0, 4, 7, 3)]) - - return verts, faces - - -def update_flooring(self, context): - o = context.object - mats = [i for i in o.data.materials] - - pre_scale = tuple(o.scale.copy()) - - if tuple(o.scale.copy()) != (1.0, 1.0, 1.0) and o.jv_object_add == "convert": - bpy.ops.object.transform_apply(scale=True) - - dim = [i + 0.1 for i in tuple(o.dimensions)] - coords = [0, 0, 0] - verts, faces = [], [] - - # generate faces and vertices - if o.jv_object_add == "add": - verts, faces = create_flooring(o.jv_flooring_types, o.jv_wood_flooring_types, o.jv_tile_types, o.jv_over_width, - o.jv_over_length, o.jv_b_width, o.jv_b_length, o.jv_b_length_s, - o.jv_is_length_vary, o.jv_length_vary, o.jv_num_boards, o.jv_space_l, - o.jv_space_w, o.jv_spacing, o.jv_t_width, o.jv_t_length, o.jv_is_offset, - o.jv_offset, o.jv_is_random_offset, o.jv_offset_vary, o.jv_t_width_s, - o.jv_is_width_vary, o.jv_width_vary, o.jv_max_boards, o.jv_is_ran_thickness, - o.jv_ran_thickness, o.jv_thickness, o.jv_hb_direction) - elif o.jv_object_add == "convert": - verts, faces = create_flooring(o.jv_flooring_types, o.jv_wood_flooring_types, o.jv_tile_types, dim[0], dim[1], - o.jv_b_width, o.jv_b_length, o.jv_b_length_s, o.jv_is_length_vary, - o.jv_length_vary, o.jv_num_boards, o.jv_space_l, o.jv_space_w, o.jv_spacing, - o.jv_t_width, o.jv_t_length, o.jv_is_offset, o.jv_offset, o.jv_is_random_offset, - o.jv_offset_vary, o.jv_t_width_s, o.jv_is_width_vary, o.jv_width_vary, - o.jv_max_boards, o.jv_is_ran_thickness, o.jv_ran_thickness, o.jv_thickness, - o.jv_hb_direction) - - emesh = None - mesh = None - if o.jv_object_add in ("convert", "add"): - emesh = o.data - mesh = bpy.data.meshes.new(name="flooring") - mesh.from_pydata(verts, [], faces) - mesh.update(calc_edges=True) - - if o.jv_object_add == "convert": - if o.jv_cut_name == "none": # create cutter object - - for ob in context.scene.objects: - ob.select = False - - cutter = bpy.data.objects.new(o.name + "_cutter", o.data.copy()) - context.scene.objects.link(cutter) - cutter.location = o.location.copy() - cutter.rotation_euler = o.rotation_euler.copy() - cutter.scale = o.scale.copy() - - o.jv_cut_name = cutter.name - cutter.select = True - context.scene.objects.active = cutter - - bpy.ops.object.modifier_add(type="SOLIDIFY") - pos = len(cutter.modifiers) - 1 - cutter.modifiers[pos].offset = 0 - cutter.modifiers[pos].thickness = 1 - - o.select = True - context.scene.objects.active = o - - # getting extreme position - tup_verts = [vert.co.to_tuple() for vert in o.data.vertices] # get vertex data - x, y, z = None, None, None - for i in tup_verts: # find smallest x and z values - if x is None: - x = i[0] - elif i[0] < x: - x = i[0] - - if z is None: - z = i[2] - elif i[2] < z: - z = i[2] - - if y is None: - y = i[1] - elif i[1] < y: - y = i[1] - - position = o.matrix_world * Vector((x, y, z)) # get world space - coords = position - elif o.jv_cut_name in bpy.data.objects: - (bpy.data.objects[o.jv_cut_name]).name = o.name + "_cutter" - o.jv_cut_name = o.name + "_cutter" - coords = o.location.copy() - else: - self.report({"ERROR"}, "Can't Find Cutter Object") - bpy.ops.object.delete() - - for i in bpy.data.objects: - if i.data == emesh: - i.data = mesh - - emesh.user_clear() - bpy.data.meshes.remove(emesh) - - # modifiers - if o.jv_is_bevel and o.jv_flooring_types == "1": - bpy.ops.object.modifier_add(type="BEVEL") - pos = len(o.modifiers) - 1 - o.modifiers[pos].segments = o.jv_bevel_res - o.modifiers[pos].width = o.jv_bevel_amo - bpy.ops.object.modifier_apply(apply_as="DATA", modifier=o.modifiers[pos].name) - - # set position - if o.jv_object_add == "convert": - o.location = coords - cutter = bpy.data.objects[o.jv_cut_name] - - for ob in bpy.data.objects: - ob.select = False - - cursor = context.scene.cursor_location.copy() - o.select = True - bpy.context.scene.objects.active = o - bpy.ops.view3d.snap_cursor_to_selected() - o.select = False - - if o.jv_is_cut == "none": - cutter.select = True - bpy.context.scene.objects.active = cutter - bpy.ops.object.origin_set(type="ORIGIN_CURSOR") - bpy.ops.object.move_to_layer(layers=[i >= 19 for i in range(20)]) - cutter.select = False - o.jv_is_cut = "cut" - - o.select = True - bpy.context.scene.objects.active = o - cutter.location = o.location.copy() - cutter.rotation_euler = o.rotation_euler.copy() - - if pre_scale != (1.0, 1.0, 1.0): - layers = [i for i in bpy.context.scene.layers] - counter = 1 - true = [] - - for i in layers: - if i: - true.append(counter) - counter += 1 - - for area in bpy.context.screen.areas: - if area.type == 'VIEW_3D': - override = bpy.context.copy() - override['area'] = area - bpy.ops.view3d.layers(override, nr=20, toggle=True) - - cutter.select = True - o.select = False - bpy.context.scene.objects.active = cutter - cutter.scale = (pre_scale[0], pre_scale[2], pre_scale[1]) - bpy.ops.object.transform_apply(scale=True) - cutter.select = False - - for area in bpy.context.screen.areas: - if area.type == 'VIEW_3D': - override = bpy.context.copy() - override['area'] = area - for i in true: - bpy.ops.view3d.layers(override, nr=i, extend=True, toggle=True) - - bpy.ops.view3d.layers(override, nr=20, extend=True, toggle=True) - bpy.context.scene.objects.active = o - o.select = True - bpy.context.scene.cursor_location = cursor # change cursor location back to original - - # cut - apply_modifier_boolean(bpy, o, o.jv_cut_name) - - # create grout - grout_ob = None - if o.jv_flooring_types == "2": - if o.jv_object_add == "add": - verts2, faces2 = tile_grout(o.jv_over_width, o.jv_over_length, o.jv_grout_depth, o.jv_thickness) - else: - c = bpy.data.objects[o.jv_cut_name] - verts2, faces2 = tile_grout(c.dimensions[0], c.dimensions[1], o.jv_grout_depth, o.jv_thickness) - - grout = bpy.data.meshes.new(name="grout") - grout.from_pydata(verts2, [], faces2) - grout_ob = bpy.data.objects.new("grout", grout) - context.scene.objects.link(grout_ob) - grout_ob.rotation_euler = o.rotation_euler - grout_ob.location = o.location - - if o.jv_object_add == "convert": - o.select = False - grout_ob.select = True - context.scene.objects.active = grout_ob - - apply_modifier_boolean(bpy, grout_ob, o.jv_cut_name) - - grout_ob.select = False - o.select = True - context.scene.objects.active = o - - # cut tile or herringbone wood - if o.jv_object_add == "add" and (o.jv_flooring_types == "2" and o.jv_tile_types in ("2", "4")) or \ - (o.jv_flooring_types == "1" and o.jv_wood_flooring_types in ("3", "4")): - verts3, faces3 = tile_cutter(o.jv_over_width, o.jv_over_length) - - cutter_me = bpy.data.meshes.new("cutter") - cutter_me.from_pydata(verts3, [], faces3) - cutter_ob = bpy.data.objects.new("cutter", cutter_me) - context.scene.objects.link(cutter_ob) - cutter_ob.location = o.location - cutter_ob.rotation_euler = o.rotation_euler - cutter_ob.scale = o.scale - - for ob in bpy.data.objects: - ob.select = False - - o.select = True - context.scene.objects.active = o - apply_modifier_boolean(bpy, o, cutter_ob.name) - o.select = False - - # cut grout if needed - if o.jv_flooring_types == "2": - grout_ob.select = True - context.scene.objects.active = grout_ob - apply_modifier_boolean(bpy, grout_ob, cutter_ob.name) - grout_ob.select = False - - cutter_ob.select = True - context.scene.objects.active = cutter_ob - bpy.ops.object.delete() - o.select = True - context.scene.objects.active = o - - # add materials - if o.jv_flooring_types == "2": # grout material - enter = True - for i in mats: - if "grout_" in i.name or len(mats) >= 2: - enter = False - if enter: - mat = bpy.data.materials.new("grout_temp") - mat.use_nodes = True - grout_ob.data.materials.append(mat) - else: - mat = bpy.data.materials.get(mats[1].name) - grout_ob.data.materials.append(mat) - - if o.jv_is_material or o.jv_flooring_types == "2": - enter = True - for i in mats: - if "flooring_" in i.name or len(mats) >= 2: - enter = False - if enter: - mat = bpy.data.materials.new("flooring_" + o.name) - mat.use_nodes = True - o.data.materials.append(mat) - else: - mat = bpy.data.materials.get(mats[0].name) - o.data.materials.append(mat) - - # join grout - if o.jv_flooring_types == "2": - vertex_group(self, context) - for ob in bpy.data.objects: - ob.select = False - - grout_ob.select = True - o.select = True - context.scene.objects.active = o - bpy.ops.object.join() - - for i in mats: - if i.name not in o.data.materials: - mat = bpy.data.materials[i.name] - o.data.materials.append(mat) - - # uv unwrap - if o.jv_is_unwrap: - unwrap_object(self, context) - if o.jv_is_random_uv: - random_uvs(self, context) - - -def vertex_group(self, context): - o = context.object - - for i in bpy.data.objects: - i.select = False - - o.select = True - bpy.context.scene.objects.active = o - - for area in bpy.context.screen.areas: - if area.type == 'VIEW_3D': - for region in area.regions: - if region.type == 'WINDOW': - bpy.ops.object.editmode_toggle() - if "JARCH" in o.vertex_groups: - group = o.vertex_groups.get("JARCH") - o.vertex_groups.remove(group) - - bpy.ops.object.vertex_group_add() - bpy.ops.object.vertex_group_assign() - active = o.vertex_groups.active - active.name = "JARCH" - bpy.ops.object.editmode_toggle() - - -def flooring_material(self, context): - o = bpy.context.object - if o.jv_col_image == "": - self.report({"ERROR"}, "JARCH Vis: No Color Image Filepath") - return - if o.jv_is_bump and o.jv_norm_image == "": - self.report({"ERROR"}, "JARCH Vis: No Normal Image Filepath") - return - - extra_rot = None - if o.jv_flooring_types == "1" and o.jv_wood_flooring_types in ("3", "4"): - extra_rot = 45 - - mat = image_material(bpy, o.jv_im_scale, o.jv_col_image, o.jv_norm_image, o.jv_bump_amo, o.jv_is_bump, - "flooring_use", True, 0.1, 0.05, o.jv_is_rotate, extra_rot) - - if mat is not None: - if len(o.data.materials) == 0: - o.data.materials.append(mat.copy()) - else: - o.data.materials[0] = mat.copy() - o.data.materials[0].name = "flooring_" + o.name - else: - self.report({"ERROR"}, "JARCH Vis: Image(s) Not Found, Make Sure Path Is Correct") - return - - if o.jv_flooring_types == "2" and len(o.data.materials) >= 2: - mat2 = mortar_material(bpy, o.jv_mortar_color, o.jv_mortar_bump, o.data.materials[1].name) - o.data.materials[1] = mat2.copy() - o.data.materials[1].name = "mortar_" + o.name - - # remove extra materials - for i in bpy.data.materials: - if i.users == 0: - bpy.data.materials.remove(i) - - -class FlooringMaterials(bpy.types.Operator): - bl_idname = "mesh.jv_flooring_materials" - bl_label = "Generate\\Update Materials" - bl_options = {"UNDO", "INTERNAL"} - - def execute(self, context): - flooring_material(self, context) - return {"FINISHED"} - - -class FlooringPanel(bpy.types.Panel): - bl_idname = "OBJECT_PT_jv_floring" - bl_label = "JARCH Vis: Flooring" - bl_space_type = "VIEW_3D" - bl_region_type = "TOOLS" - bl_category = "JARCH Vis" - - def draw(self, context): - layout = self.layout - if bpy.context.mode == "EDIT_MESH": - layout.label("JARCH Vis: Flooring Doesn't Work In Edit Mode", icon="ERROR") - else: - o = context.object - if o is not None: - if o.type == "MESH": - if o.jv_internal_type in ("flooring", ""): - if o.jv_object_add in ("convert", "add"): - layout.label("Material:") - layout.prop(o, "jv_flooring_types", icon="MATERIAL") - layout.label("Types:") - - if o.jv_flooring_types == "1": - layout.prop(o, "jv_wood_flooring_types", icon="OBJECT_DATA") - elif o.jv_flooring_types == "2": - layout.prop(o, "jv_tile_types", icon="OBJECT_DATA") - layout.separator() - if o.jv_object_add == "add": - layout.prop(o, "jv_over_width") - layout.prop(o, "jv_over_length") - layout.separator() - - # width and lengths - layout.prop(o, "jv_thickness") - - if o.jv_flooring_types == "1": - layout.prop(o, "jv_b_width") - - if o.jv_wood_flooring_types == "1": - layout.prop(o, "jv_b_length") - layout.separator() - - if o.jv_wood_flooring_types == "1": - layout.prop(o, "jv_is_length_vary", icon="NLA") - if o.jv_is_length_vary: - layout.prop(o, "jv_length_vary") - layout.prop(o, "jv_max_boards") - layout.separator() - - layout.prop(o, "jv_is_width_vary", icon="UV_ISLANDSEL") - if o.jv_is_width_vary: - layout.prop(o, "jv_width_vary") - layout.separator() - - layout.prop(o, "jv_is_ran_thickness", icon="RNDCURVE") - if o.jv_is_ran_thickness: - layout.prop(o, "jv_ran_thickness") - - layout.separator() - layout.prop(o, "jv_space_w") - layout.prop(o, "jv_space_l") - layout.separator() - elif o.jv_wood_flooring_types in ("3", "4"): - layout.prop(o, "jv_b_length_s") - layout.prop(o, "jv_hb_direction") - layout.separator() - - if o.jv_wood_flooring_types != "1": - layout.prop(o, "jv_spacing") - - if o.jv_wood_flooring_types == "2": - layout.prop(o, "jv_num_boards") - - # bevel - layout.prop(o, "jv_is_bevel", icon="MOD_BEVEL") - if o.jv_is_bevel: - layout.prop(o, "jv_bevel_res", icon="OUTLINER_DATA_CURVE") - layout.prop(o, "jv_bevel_amo") - layout.separator() - - elif o.jv_flooring_types == "2": - if o.jv_tile_types != "4": - layout.prop(o, "jv_t_width") - layout.prop(o, "jv_t_length") - layout.separator() - else: - layout.prop(o, "jv_t_width_s") - layout.separator() - - if o.jv_tile_types == "1": - layout.prop(o, "jv_is_offset", icon="OOPS") - if o.jv_is_offset: - layout.prop(o, "jv_is_random_offset", icon="NLA") - if not o.jv_is_random_offset: - layout.prop(o, "jv_offset") - else: - layout.prop(o, "jv_offset_vary") - layout.separator() - - layout.prop(o, "jv_grout_depth") - layout.prop(o, "jv_spacing") - layout.separator() - - layout.prop(o, "jv_is_unwrap", icon="GROUP_UVS") - if o.jv_is_unwrap: - layout.prop(o, "jv_is_random_uv", icon="RNDCURVE") - layout.separator() - - if context.scene.render.engine == "CYCLES": - layout.prop(o, "jv_is_material", icon="MATERIAL") - else: - layout.label("Materials Only Supported With Cycles", icon="POTATO") - - if o.jv_is_material and context.scene.render.engine == "CYCLES": - layout.separator() - layout.prop(o, "jv_col_image", icon="COLOR") - layout.prop(o, "jv_is_bump", icon="SMOOTHCURVE") - - if o.jv_is_bump: - layout.prop(o, "jv_norm_image", icon="TEXTURE") - layout.prop(o, "jv_bump_amo") - layout.prop(o, "jv_im_scale", icon="MAN_SCALE") - layout.prop(o, "jv_is_rotate", icon="MAN_ROT") - - if o.jv_flooring_types == "2": - layout.separator() - layout.prop(o, "jv_mortar_color", icon="COLOR") - layout.prop(o, "jv_mortar_bump") - - layout.separator() - layout.operator("mesh.jv_flooring_materials", icon="MATERIAL") - layout.separator() - layout.prop(o, "jv_is_preview", icon="SCENE") - - layout.separator() - layout.operator("mesh.jv_flooring_update", icon="FILE_REFRESH") - layout.operator("mesh.jv_flooring_delete", icon="CANCEL") - layout.operator("mesh.jv_flooring_add", icon="MESH_GRID") - else: - layout.operator("mesh.jv_flooring_convert", icon="FILE_REFRESH") - layout.operator("mesh.jv_flooring_add", icon="MESH_GRID") + x += x_leg + gap_dif + d + if move_down: + y -= y_leg + (gap / 2) else: - layout.label("This Is Already A JARCH Vis Object", icon="INFO") - layout.operator("mesh.jv_flooring_add", icon="MESH_GRID") - else: - layout.label("Only Mesh Objects Can Be Used", icon="ERROR") - layout.operator("mesh.jv_flooring_add", icon="MESH_GRID") + y += y_leg + (gap / 2) + + move_down = not move_down + + start_y += (2*y_leg) + gap + + @staticmethod + def _octagons(dims: tuple, props, verts, faces): # with dots since octagons cannot fit together otherwise + side_length, gap = props.side_length, props.gap_uniform + gap_dif = gap * cos(radians(30)) + x_leg = side_length / 2 + y_leg = x_leg / tan(radians(22.5)) + + dot_s = ((2 * y_leg + gap) - 2*x_leg - 2*gap_dif) / 2 + + y = y_leg + upper_x, upper_y = dims[0] + y_leg, dims[1] + (2*y_leg) + while y < upper_y: + x = x_leg + while x < upper_x: + verts += [ # swapping x_leg with y_leg is on purpose + (x-x_leg, y-y_leg, 0), + (x+x_leg, y-y_leg, 0), + (x+y_leg, y-x_leg, 0), + (x+y_leg, y+x_leg, 0), + + (x+x_leg, y+y_leg, 0), + (x-x_leg, y+y_leg, 0), + (x-y_leg, y+x_leg, 0), + (x-y_leg, y-x_leg, 0) + ] + + p = len(verts) - 8 + faces.append((p, p+7, p+6, p+5, p+4, p+3, p+2, p+1)) + + x += y_leg + (gap / 2) + y -= x_leg + gap_dif + + verts += [ + (x, y, 0), + (x-dot_s, y-dot_s, 0), + (x, y-dot_s-dot_s, 0), + (x+dot_s, y-dot_s, 0) + ] + + p = len(verts) - 4 + faces.append((p, p+3, p+2, p+1)) + + x += y_leg + (gap / 2) + y += x_leg + gap_dif + + y += (2*y_leg) + gap + + @staticmethod + def _corridor(dims: tuple, props, verts, faces): + length, width, gap = props.tile_length, props.tile_width, props.gap_uniform + half_width = props.alternating_row_width + + first_length_for_fixed_offset = length * (props.row_offset / 100) + if first_length_for_fixed_offset == 0: + first_length_for_fixed_offset = length + + offset_length_variance = JVFlooring._create_variance_function(props.vary_row_offset, + length / 2, + props.row_offset_variance) + + y = 0 + large = True + upper_x, upper_y = dims + while y < upper_y: + x = 0 + + if large: + cur_width = width else: - layout.operator("mesh.jv_flooring_add", icon="MESH_GRID") - - -class FlooringAdd(bpy.types.Operator): - bl_idname = "mesh.jv_flooring_add" - bl_label = "Add Flooring" - bl_description = "JARCH Vis: Flooring Generator" - - @classmethod - def poll(cls, context): - return context.mode == "OBJECT" - - def execute(self, context): - bpy.ops.mesh.primitive_cube_add() - o = bpy.context.scene.objects.active - o.jv_internal_type = "flooring" - o.jv_object_add = "add" - return {"FINISHED"} - - -class FlooringConvert(bpy.types.Operator): - bl_idname = "mesh.jv_flooring_convert" - bl_label = "Convert To Flooring" - bl_options = {"UNDO", "INTERNAL"} - - def execute(self, context): - o = context.object - o.jv_internal_type = "flooring" - o.jv_object_add = "convert" - return {"FINISHED"} - - -class FlooringUpdate(bpy.types.Operator): - bl_idname = "mesh.jv_flooring_update" - bl_label = "Update Flooring" - bl_options = {"UNDO", "INTERNAL"} - - def execute(self, context): - update_flooring(self, context) - return {"FINISHED"} - - -class FlooringDelete(bpy.types.Operator): - bl_idname = "mesh.jv_flooring_delete" - bl_label = "Delete Flooring" - bl_options = {"UNDO", "INTERNAL"} - - def execute(self, context): - o = context.object - convert = False - - # delete cutter if converted mesh - if o.jv_internal_type == "flooring" and o.jv_object_add == "convert" and o.jv_cut_name in bpy.data.objects: - cutter = bpy.data.objects[o.jv_cut_name] - o.select = False - pre_layers = list(context.scene.layers) - al = context.scene.active_layer - - context.scene.layers = [i >= 19 for i in range(20)] - cutter.select = True - context.scene.objects.active = cutter - bpy.ops.object.modifier_remove(modifier="Solidify") - bpy.ops.object.move_to_layer(layers=[i == al for i in range(20)]) - convert = True - cutter.select = False - cutter.jv_object_add = "none" - cutter.jv_internal_type = "" - - context.scene.layers = pre_layers - - m_name = o.name - c_name = o.jv_cut_name - o.select = True - bpy.context.scene.objects.active = o - bpy.ops.object.delete() - - for i in bpy.data.materials: - if i.users == 0: - bpy.data.materials.remove(i) - - if convert: - bpy.data.objects[c_name].name = m_name - - return {"FINISHED"} - - -def register(): - bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() + cur_width = half_width + + trimmed_width = min(cur_width, upper_y-y) + while x < upper_x: + cur_length = length + + if x == 0 and not large: + if props.vary_row_offset: + cur_length = offset_length_variance() + else: + cur_length = first_length_for_fixed_offset + + trimmed_length = min(cur_length, upper_x-x) + + verts += [ + (x, y, 0), + (x+trimmed_length, y, 0), + (x+trimmed_length, y+trimmed_width, 0), + (x, y+trimmed_width, 0) + ] + + p = len(verts) - 4 + faces.append((p, p+3, p+2, p+1)) + + x += cur_length + gap + + large = not large + y += cur_width + gap diff --git a/jv_materials.py b/jv_materials.py deleted file mode 100644 index 8f15ae8..0000000 --- a/jv_materials.py +++ /dev/null @@ -1,606 +0,0 @@ -# This file is part of JARCH Vis -# -# JARCH Vis is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# JARCH Vis is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with JARCH Vis. If not, see . - -from math import radians - - -# glossy / diffuse mix -def glossy_diffuse_material(bpy, dif_col, glos_col, rough, mix, name): - mat = bpy.data.materials.new(name) - - mat.use_nodes = True - nodes = mat.node_tree.nodes - rgba = list(dif_col) - rgba.append(1.0) - mat.diffuse_color = dif_col - g_rgba = list(glos_col) - g_rgba.append(1.0) - - # shaders - node = nodes["Diffuse BSDF"] - node.location = -100, 400 - node.inputs[0].default_value = rgba - node = nodes.new("ShaderNodeBsdfGlossy") - node.name = "Glossy" - node.inputs[1].default_value = rough - node.inputs[0].default_value = g_rgba - node.location = -100, 200 - node = nodes.new("ShaderNodeMixShader") - node.name = "Mix" - node.location = 100, 300 - node.inputs[0].default_value = mix - - links = [["Diffuse BSDF", 0, "Mix", 1], ["Glossy", 0, "Mix", 2], ["Mix", 0, "Material Output", 0]] - - for link in links: - o = nodes[link[0]].outputs[link[1]] - i = nodes[link[2]].inputs[link[3]] - mat.node_tree.links.new(o, i) - - return mat - - -# wood -def image_material(bpy, im_scale, c_image, n_image, bump_amo, is_bump, name, is_gloss, glossy, mix, rotate, extra_rot): - color_loaded, normal_loaded = False, False - - for i in bpy.data.images: - if i.filepath == c_image and not color_loaded: - col_im = i - color_loaded = True # check if color image already loaded - elif i.filepath == n_image and not normal_loaded and is_bump: - norm_im = i - normal_loaded = True # check if normal image already loaded - - if not color_loaded: # load the images otherwise - try: - col_im = bpy.data.images.load(c_image) - except RuntimeError: - return None - if not normal_loaded and is_bump: - try: - norm_im = bpy.data.images.load(n_image) - except RuntimeError: - return None - - mat = bpy.data.materials.new(name) - mat.use_nodes = True - nodes = mat.node_tree.nodes - node = nodes.new("ShaderNodeTexCoord") - node.location = -700, 0 - node.name = "uv_coords" - node = nodes.new("ShaderNodeMapping") - node.location = -500, 0 - node.name = "scale" - node.scale = (im_scale, im_scale, im_scale) - - if rotate: - node.rotation = (0.0, 0.0, radians(90)) - if extra_rot is not None: - node.rotation = (0.0, 0.0, node.rotation[2] + radians(extra_rot)) - - node = nodes.new("ShaderNodeTexImage") - node.location = -100, 150 - node.name = "color_image" - node.image = col_im - node = nodes.new("ShaderNodeTexImage") - node.location = -100, -150 - node.name = "normal_image" - if is_bump: - node.image = norm_im - - node = nodes["Diffuse BSDF"] - node.location = 100, 100 - node.name = "diffuse" - - if is_gloss: - node = nodes.new("ShaderNodeBsdfGlossy") - node.location = 100, -50 - node.name = "glossy" - node.inputs[1].default_value = glossy - node = nodes.new("ShaderNodeMixShader") - node.location = 300, 50 - node.name = "mix" - node.inputs[0].default_value = mix - - node = nodes.new("ShaderNodeNormalMap") - node.location = 100, -200 - node.name = "normal_map" - node.inputs[0].default_value = bump_amo - node = nodes.new("ShaderNodeMath") - node.location = 300, -200 - node.name = "math" - node.operation = "MULTIPLY" - - if is_bump: - node.inputs[1].default_value = 1.0 - else: - node.inputs[1].default_value = 0.0 - - node = nodes["Material Output"] - node.location = 500, 0 - links = [["uv_coords", 2, "scale", 0], ["scale", 0, "color_image", 0], ["scale", 0, "normal_image", 0], - ["color_image", 0, "diffuse", 0], ["normal_image", 0, "normal_map", 1], ["normal_map", 0, "math", 0], - ["math", 0, "Material Output", 2]] - - if is_gloss: - links += [["diffuse", 0, "mix", 1], ["glossy", 0, "mix", 2], ["mix", 0, "Material Output", 0]] - - for link in links: - o = nodes[link[0]].outputs[link[1]] - i = nodes[link[2]].inputs[link[3]] - mat.node_tree.links.new(o, i) - - return mat - - -def brick_material(bpy, color_style, color1, color2, color3, color_sharp, color_scale, bump_type, brick_bump, - bump_scale, name): - - mat = bpy.data.materials.new(name) - mat.use_nodes = True - nodes = mat.node_tree.nodes - - # color rgba - c1l, c2l, c3l = list(color1), list(color2), list(color3) - c1l.append(1.0) - c2l.append(1.0) - c3l.append(1.0) # create rgba lists for the three colors - mat.diffuse_color = color1 - - # colors - if color_style == "constant": # add uv_coords if single color since it wouldn't get it otherwise - nodes["Diffuse BSDF"].inputs[0].default_value = c1l - if bump_type != "4": - node = nodes.new("ShaderNodeTexCoord") - node.name = "uv_coords" - node.location = -1700, 400 - - elif color_style == "speckled": - node = nodes.new("ShaderNodeTexCoord") - node.name = "uv_coords" - node.location = -1500, 200 - node = nodes.new("ShaderNodeMapping") - node.name = "scale" - node.location = -1300, 200 - node.scale = (color_scale * 2.0, color_scale * 2.0, color_scale * 2.0) - node.label = "Color Scale" - - node = nodes.new("ShaderNodeTexNoise") - node.name = "c_noise1" - node.location = -1200, 400 - node.inputs[1].default_value = 2.0 - node = nodes.new("ShaderNodeTexNoise") - node.name = "c_noise2" - node.location = -1200, 600 - node = nodes.new("ShaderNodeMixRGB") - node.name = "cn1|cn2" - node.location = -1000, 475 - node = nodes.new("ShaderNodeBrightContrast") - node.name = "color_vary" - node.location = -850, 400 - node.inputs[2].default_value = color_sharp * 2.0 - node.label = "Color Sharpness" - - node = nodes.new("ShaderNodeRGB") - node.name = "color1" - node.location = -900, 50 - node.outputs[0].default_value = c1l - node.label = "Color 1" - node = nodes.new("ShaderNodeRGB") - node.name = "color2" - node.location = -900, 250 - node.outputs[0].default_value = c2l - node.label = "Color 2" - node = nodes.new("ShaderNodeMixRGB") - node.name = "c1|c2" - node.location = -700, 300 - node = nodes.new("ShaderNodeMixRGB") - node.name = "c1Xc2" - node.location = -700, 100 - node.blend_type = "MULTIPLY" - - node = nodes.new("ShaderNodeTexNoise") - node.name = "c_noise3" - node.location = -1000, 700 - node.inputs[1].default_value = 600.0 - node = nodes.new("ShaderNodeBrightContrast") - node.name = "contrast" - node.location = -800, 700 - node.inputs[2].default_value = 6.0 - node = nodes.new("ShaderNodeTexNoise") - node.name = "c_noise4" - node.location = -800, 575 - node.inputs[1].default_value = 1000.0 - node = nodes.new("ShaderNodeMixRGB") - node.name = "con|cn4" - node.location = -600, 600 - node.inputs[1].default_value = (0.0, 0.0, 0.0, 1.0) - - node = nodes.new("ShaderNodeBrightContrast") - node.name = "contrast2" - node.location = -400, 400 - node.inputs[2].default_value = 4.0 - node = nodes.new("ShaderNodeMixRGB") - node.name = "final|" - node.location = -200, 300 - - links = [["uv_coords", 2, "scale", 0], ["uv_coords", 2, "c_noise3", 0], ["uv_coords", 2, "c_noise4", 0], - ["scale", 0, "c_noise1", 0], ["scale", 0, "c_noise2", 0], ["c_noise1", 1, "cn1|cn2", 2], - ["c_noise2", 1, "cn1|cn2", 1], ["cn1|cn2", 0, "color_vary", 0], ["color_vary", 0, "c1|c2", 0], - ["color1", 0, "c1|c2", 1], ["color2", 0, "c1|c2", 2], ["color1", 0, "c1Xc2", 1], - ["color2", 0, "c1Xc2", 2], ["c1|c2", 0, "final|", 1], ["c1Xc2", 0, "final|", 2], - ["c_noise3", 1, "contrast", 0], ["contrast", 0, "con|cn4", 0], ["c_noise4", 1, "con|cn4", 2], - ["con|cn4", 0, "contrast2", 0], ["contrast2", 0, "final|", 0], ["final|", 0, "Diffuse BSDF", 0]] - for i in links: - o = nodes[i[0]].outputs[i[1]] - i = nodes[i[2]].inputs[i[3]] - mat.node_tree.links.new(o, i) - - elif color_style == "multiple": - node = nodes.new("ShaderNodeTexCoord") - node.name = "uv_coords" - node.location = -1100, 300 - node = nodes.new("ShaderNodeMapping") - node.name = "scale" - node.location = -925, 35 - node.scale = [color_scale for i in range(3)] - node.label = "Color Scale" - - node = nodes.new("ShaderNodeTexNoise") - node.name = "c_noise" - node.location = -600, 600 - node.inputs[1].default_value = 4.0 - node = nodes.new("ShaderNodeBrightContrast") - node.name = "color_vary" - node.location = -400, 500 - node.inputs[2].default_value = color_sharp - node.label = "Color Sharpness" - node = nodes.new("ShaderNodeMixRGB") - node.name = "c1|c2" - node.location = -200, 450 - - node = nodes.new("ShaderNodeRGB") - node.name = "color1" - node.location = -500, 175 - node.outputs[0].default_value = c1l - node.label = "Color 1" - node = nodes.new("ShaderNodeRGB") - node.name = "color2" - node.location = -500, 375 - node.outputs[0].default_value = c2l - node.label = "Color 2" - - links = [["uv_coords", 2, "scale", 0], ["scale", 0, "c_noise", 0], ["c_noise", 1, "color_vary", 0], - ["color_vary", 0, "c1|c2", 0], ["color1", 0, "c1|c2", 2], ["color2", 0, "c1|c2", 1], - ["c1|c2", 0, "Diffuse BSDF", 0]] - for i in links: - o = nodes[i[0]].outputs[i[1]] - i = nodes[i[2]].inputs[i[3]] - mat.node_tree.links.new(o, i) - - elif color_style == "extreme": - node = nodes.new("ShaderNodeTexCoord") - node.name = "uv_coords" - node.location = -1700, 400 - node = nodes.new("ShaderNodeMapping") - node.name = "scale" - node.location = -1500, 400 - node.scale = (color_scale, color_scale, color_scale) - node.label = "Color Scale" - - node = nodes.new("ShaderNodeTexNoise") - node.name = "c_noise" - node.location = -1100, 600 - node.inputs[1].default_value = 10.0 - node = nodes.new("ShaderNodeRGB") - node.name = "color3" - node.location = -1150, 800 - node.outputs[0].default_value = c3l - node.label = "Color 3" - node = nodes.new("ShaderNodeRGB") - node.name = "color2" - node.location = -1050, 400 - node.outputs[0].default_value = c2l - node.label = "Color 2" - node = nodes.new("ShaderNodeRGB") - node.name = "color1" - node.location = -1050, 200 - node.outputs[0].default_value = c1l - node.label = "Color 1" - - node = nodes.new("ShaderNodeBrightContrast") - node.name = "c_v1" - node.location = -900, 525 - node.inputs[2].default_value = color_sharp - node.label = "Color Sharpness 1" - node = nodes.new("ShaderNodeMixRGB") - node.name = "c1|c2" - node.location = -700, 400 - - node = nodes.new("ShaderNodeTexNoise") - node.name = "c_noise2" - node.location = -900, 800 - node = nodes.new("ShaderNodeBrightContrast") - node.name = "c_v2" - node.location = -700, 700 - node.inputs[2].default_value = color_sharp * 1.25 - node.label = "Color Sharpness 2" - node = nodes.new("ShaderNodeMixRGB") - node.name = "c3|c2c1" - node.location = -500, 600 - node = nodes.new("ShaderNodeTexNoise") - node.name = "c_noise3" - node.location = -600, 900 - node.inputs[1].default_value = 50.0 - - node = nodes.new("ShaderNodeBrightContrast") - node.name = "c_v3" - node.location = -400, 800 - node.inputs[2].default_value = color_sharp * 1.25 - node.label = "Color Sharpness 3" - node = nodes.new("ShaderNodeMixRGB") - node.name = "c1|c2.2" - node.location = -400, 400 - node = nodes.new("ShaderNodeMixRGB") - node.name = "final|" - node.location = -250, 600 - - links = [["uv_coords", 2, "scale", 0], ["scale", 0, "c_noise", 0], ["scale", 0, "c_noise2", 0], - ["scale", 0, "c_noise3", 0], ["c_noise", 1, "c_v1", 0], ["c_v1", 0, "c1|c2", 0], - ["color1", 0, "c1|c2", 2], ["color2", 0, "c1|c2", 1], ["color1", 0, "c1|c2.2", 2], - ["color2", 0, "c1|c2.2", 1], ["color3", 0, "c3|c2c1", 1], ["c_noise2", 1, "c_v2", 0], - ["c_v2", 0, "c3|c2c1", 0], ["c1|c2", 0, "c3|c2c1", 2], ["c_noise3", 1, "c_v3", 0], - ["c_v3", 0, "final|", 0], ["c3|c2c1", 0, "final|", 2], ["c1|c2.2", 0, "final|", 1], - ["final|", 0, "Diffuse BSDF", 0]] - for i in links: - o = nodes[i[0]].outputs[i[1]] - i = nodes[i[2]].inputs[i[3]] - mat.node_tree.links.new(o, i) - - # BUMP ------------------------------------------------->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - if bump_type == "1": # dimpled - node = nodes.new("ShaderNodeMapping") - node.name = "b_scale" - node.location = -700, -100 - node.scale = (bump_scale, bump_scale, bump_scale) - node.label = "Scale Amount" - node.label = "Bump Scale" - node = nodes.new("ShaderNodeTexNoise") - node.name = "b_noise" - node.location = -300, 0 - node.inputs[1].default_value = 400.0 - - node = nodes.new("ShaderNodeTexNoise") - node.name = "b_noise2" - node.location = -300, -200 - node.inputs[1].default_value = 200.0 - node = nodes.new("ShaderNodeMixRGB") - node.name = "bn|bn2" - node.location = -100, -100 - node = nodes.new("ShaderNodeMath") - node.name = "b_amount" - node.location = 100, 0 - node.operation = "MULTIPLY" - node.inputs[1].default_value = brick_bump - node.label = "Bump Amount" - - links = [["uv_coords", 3, "b_scale", 0], ["b_scale", 0, "b_noise", 0], ["b_scale", 0, "b_noise2", 0], - ["b_noise", 1, "bn|bn2", 1], ["b_noise2", 1, "bn|bn2", 2], ["bn|bn2", 0, "b_amount", 0], - ["b_amount", 0, "Material Output", 2]] - for i in links: - o = nodes[i[0]].outputs[i[1]] - i = nodes[i[2]].inputs[i[3]] - mat.node_tree.links.new(o, i) - - elif bump_type == "2": # ridges - nodes = mat.node_tree.nodes - node = nodes.new("ShaderNodeMapping") - node.name = "b_scale" - node.location = -800, -100 - node.rotation = (radians(-45.0), 0.0, 0.0) - node.scale = (bump_scale, bump_scale, bump_scale) - node.label = "Bump Scale" - node = nodes.new("ShaderNodeTexWave") - node.name = "b_wave" - node.location = -400, 0 - node.inputs[1].default_value = 100.0 - node = nodes.new("ShaderNodeTexNoise") - node.name = "b_noise" - node.location = -400, -200 - node.inputs[1].default_value = 400.0 - node.inputs[3].default_value = 1.0 - - node = nodes.new("ShaderNodeMixRGB") - node.name = "bw|bn" - node.location = -200, -100 - node.blend_type = "ADD" - node.inputs[0].default_value = 1.0 - node = nodes.new("ShaderNodeMath") - node.name = "b_amount" - node.location = 100, 0 - node.operation = "MULTIPLY" - node.inputs[1].default_value = brick_bump - node.label = "Bump Amount" - - links = [["uv_coords", 2, "b_scale", 0], ["b_scale", 0, "b_noise", 0], ["b_scale", 0, "b_wave", 0], - ["b_noise", 1, "bw|bn", 2], ["b_wave", 1, "bw|bn", 1], ["bw|bn", 0, "b_amount", 0], - ["b_amount", 0, "Material Output", 2]] - for i in links: - o = nodes[i[0]].outputs[i[1]] - i = nodes[i[2]].inputs[i[3]] - mat.node_tree.links.new(o, i) - - elif bump_type == "3": # flaky - node = nodes.new("ShaderNodeMapping") - node.name = "b_scale" - node.location = -1500, -700 - node.scale = (bump_scale, bump_scale, bump_scale) - node.label = "Bump Scale" - node = nodes.new("ShaderNodeMapping") - node.name = "b_scale2" - node.location = -1500, -400 - node.rotation = (radians(-45.0), 0.0, 0.0) - node.scale = (bump_scale, bump_scale, bump_scale) - node.label = "Bump Scale 2" - - node = nodes.new("ShaderNodeTexNoise") - node.name = "b_noise" - node.location = -1400, -200 - node.inputs[1].default_value = 40.0 - node = nodes.new("ShaderNodeBrightContrast") - node.name = "bc" - node.location = -1200, -25 - node.inputs[2].default_value = 15.0 - node = nodes.new("ShaderNodeTexNoise") - node.name = "b_noise2" - node.location = -1200, -50 - node.inputs[1].default_value = 12.0 - node.inputs[3].default_value = 1.0 - - node = nodes.new("ShaderNodeTexWave") - node.name = "wave" - node.location = -1150, -500 - node.inputs[1].default_value = 30.0 - node.inputs[2].default_value = 4.0 - node = nodes.new("ShaderNodeBrightContrast") - node.name = "bc2" - node.location = -1000, -200 - node.inputs[2].default_value = 15.0 - node = nodes.new("ShaderNodeMixRGB") - node.name = "w|bc" - node.location = -950, -425 - node.inputs[1].default_value = (0.2, 0.2, 0.2, 1.0) - - node = nodes.new("ShaderNodeTexMusgrave") - node.name = "musgrave" - node.location = -900, -600 - node.inputs[1].default_value = 300.0 - node = nodes.new("ShaderNodeTexNoise") - node.name = "b_noise3" - node.location = -800, -900 - node.inputs[1].default_value = 1500.0 - node.inputs[3].default_value = 0.25 - node = nodes.new("ShaderNodeMixRGB") - node.name = "bc2|m1" - node.location = -750, -300 - node.inputs[1].default_value = (0.2, 0.2, 0.2, 1.0) - node = nodes.new("ShaderNodeMixRGB") - node.name = "m2|mus" - node.location = -700, -500 - node = nodes.new("ShaderNodeMixRGB") - node.name = "m2|m3" - node.location = -500, -400 - node.inputs[1].default_value = (0.2, 0.2, 0.2, 1.0) - - node = nodes.new("ShaderNodeMath") - node.name = "math" - node.location = -500, -800 - node.operation = "MULTIPLY" - node.inputs[1].default_value = 0.1 - node = nodes.new("ShaderNodeInvert") - node.name = "invert" - node.location = -350, -600 - node = nodes.new("ShaderNodeMixRGB") - node.name = "m3|math" - node.location = -150, -550 - node = nodes.new("ShaderNodeMath") - node.name = "b_amount" - node.location = 100, -200 - node.operation = "MULTIPLY" - node.inputs[1].default_value = brick_bump - node.label = "Bump Amount" - - links = [["uv_coords", 2, "b_scale", 0], ["uv_coords", 2, "b_scale2", 0], ["b_scale", 0, "b_noise", 0], - ["b_scale", 0, "b_noise2", 0], ["b_scale", 0, "musgrave", 0], ["b_scale2", 0, "wave", 0], - ["b_noise", 1, "bc", 0], ["b_noise2", 1, "bc2", 0], ["bc", 0, "w|bc", 0], ["wave", 1, "w|bc", 2], - ["bc2", 0, "bc2|m1", 0], ["w|bc", 0, "bc2|m1", 2], ["musgrave", 1, "m2|mus", 2], - ["b_noise3", 1, "math", 1], ["bc2|m1", 0, "m2|mus", 0], ["m2|mus", 0, "m2|m3", 2], - ["bc2|m1", 0, "m2|m3", 0], ["m2|m3", 0, "invert", 1], ["m2|m3", 0, "m3|math", 1], - ["invert", 0, "m3|math", 0], ["math", 0, "m3|math", 2], ["m3|math", 0, "b_amount", 0], - ["b_amount", 0, "Material Output", 2]] - for i in links: - o = nodes[i[0]].outputs[i[1]] - i = nodes[i[2]].inputs[i[3]] - mat.node_tree.links.new(o, i) - - return mat - - -def mortar_material(bpy, mortar_color, mortar_bump, name): - mat = bpy.data.materials.get(name) - nodes = mat.node_tree.nodes - mat.diffuse_color = mortar_color - - if len(nodes) > 2: # update - node = nodes["Diffuse BSDF"] - node.inputs[0].default_value = (mortar_color[0], mortar_color[1], mortar_color[2], 1.0) - node = nodes["b_amount"] - node.inputs[1].default_value = mortar_bump - else: # create - node = nodes["Diffuse BSDF"] - node.inputs[0].default_value = (mortar_color[0], mortar_color[1], mortar_color[2], 1.0) - node = nodes.new("ShaderNodeTexCoord") - node.name = "uv_coords" - node.location = -700, 200 - node = nodes.new("ShaderNodeMapping") - node.name = "scale" - node.location = -500, 200 - node.scale = (15.0, 15.0, 15.0) - - node = nodes.new("ShaderNodeTexNoise") - node.name = "b_noise" - node.location = -150, 200 - node.inputs[1].default_value = 100.0 - node = nodes.new("ShaderNodeMath") - node.name = "b_amount" - node.location = 50, 100 - node.operation = "MULTIPLY" - node.inputs[1].default_value = mortar_bump - - links = [["uv_coords", 2, "scale", 0], ["scale", 0, "b_noise", 0], ["b_noise", 1, "b_amount", 0], - ["b_amount", 0, "Material Output", 2]] - for link in links: - o = nodes[link[0]].outputs[link[1]] - i = nodes[link[2]].inputs[link[3]] - mat.node_tree.links.new(o, i) - - return mat - - -def architectural_glass_material(bpy, color, name): - mat = bpy.data.materials.new(name) - mat.use_nodes = True - nodes, links = mat.node_tree.nodes, mat.node_tree.links - - nodes.remove(nodes["Diffuse BSDF"]) - - ns = [("ShaderNodeMixShader", (100, 100)), ("ShaderNodeBsdfGlass", (-200, 100)), - ("ShaderNodeBsdfTransparent", (-200, -100)), ("ShaderNodeLightPath", (-200, 400))] - - for n in ns: - node = nodes.new(n[0]) - node.location = n[1] - - nodes["Glass BSDF"].inputs[0].default_value = color - - ls = [("Mix Shader", 0, "Material Output", 0), ("Light Path", 6, "Mix Shader", 0), - ("Glass BSDF", 0, "Mix Shader", 1), - ("Transparent BSDF", 0, "Mix Shader", 2)] - - for l in ls: - links.new(nodes[l[0]].outputs[l[1]], nodes[l[2]].inputs[l[3]]) - - return mat diff --git a/jv_operators.py b/jv_operators.py new file mode 100644 index 0000000..9a6aef5 --- /dev/null +++ b/jv_operators.py @@ -0,0 +1,293 @@ +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import bpy +import bmesh +from bpy.props import IntProperty, StringProperty +from . jv_types import get_object_type_handler +from . jv_utils import Units, determine_face_group_scale_rot_loc, determine_bisecting_planes + + +# --------------------------------------------------------------------------- +# Generic Operators +# --------------------------------------------------------------------------- +class JVAddObject(bpy.types.Operator): + bl_idname = "object.jv_add_object" + bl_label = "Add JARCH Vis Object" + bl_description = "Add JARCH Vis Object" + + object_type: StringProperty() + + def execute(self, context): + bpy.ops.mesh.primitive_cube_add() + o = context.object + + o.jv_properties.object_type = self.object_type + + return {"FINISHED"} + + +class JVDelete(bpy.types.Operator): + bl_idname = "object.jv_delete" + bl_label = "Delete Object" + bl_description = "JARCH Vis: Delete Object" + + def execute(self, context): + props = context.object.jv_properties + converted = props.convert_source_object is not None + + handler = get_object_type_handler(props.object_type_converted if converted else props.object_type) + + if handler is not None: + handler.delete(context.object.jv_properties, context) + + return {"FINISHED"} + + +class JVUpdate(bpy.types.Operator): + bl_idname = "object.jv_update" + bl_label = "Update Object" + bl_description = "JARCH Vis: Update Object" + + def execute(self, context): + props = context.object.jv_properties + converted = props.convert_source_object is not None + + handler = get_object_type_handler(props.object_type_converted if converted else props.object_type) + + if handler is not None: + handler.update(context.object.jv_properties, context) + + return {"FINISHED"} + + +class JVConvert(bpy.types.Operator): + bl_idname = "object.jv_convert" + bl_label = "Convert Object" + bl_description = "JARCH Vis: Convert Object" + + def execute(self, context): + props = context.object.jv_properties + + # if the scale isn't (1.0, 1.0, 1.0) - raise a fuse + if not all([i == 1 for i in context.object.scale]) or not all([i == 0 for i in context.object.rotation_euler]): + self.report({"ERROR"}, "The scale and rotation must be applied on this object before conversion") + return {"FINISHED"} + + if len(props.face_groups) == 0: # no face groups, so try and create one + self.report({"ERROR"}, """Please enter edit mode and create a face group before trying to convert""") + + # divide the faces up into distinct objects that can be used for the boolean process + # point each face group to the corresponding object + # create a new object that will contain the architecture and update it + else: + src = context.object + for fg in props.face_groups: + indices = set([int(i) for i in fg.face_indices.split(",") if i]) + + # collect needed vertices + faces = [] + vertices = set() + for face in src.data.polygons: + if face.index in indices: + faces.append(face) + for vi in face.vertices: + vertices.add(src.data.vertices[vi]) + + # determine loc, rot, dims + determine_face_group_scale_rot_loc(faces, list(vertices), fg) + + if fg.is_convex: + # if the face group is convex, then we can used bmesh.ops.bisect_plane to cut it + # so we have to figure out what planes need to be used to cut it based on the boundary edges + fg_mesh = bmesh.new() + fg_mesh.from_mesh(src.data) + + all_edges = {} + for face in fg_mesh.faces: + if face.index in indices: + for edge in face.edges: + # keep track of how many faces the edge is attached to + if edge in all_edges: + all_edges[edge] += 1 + else: + all_edges[edge] = 1 + + edges = set() + for edge, count in all_edges.items(): + if count == 1: + edges.add(edge) + + fg.bisecting_planes.clear() # remove any planes from a previous conversion + determine_bisecting_planes(edges, vertices, fg, faces[0].normal) + fg_mesh.free() + else: + # if the face group isn't convex, then we have to create a boolean object to use as a cutter + bm = bmesh.new() + + # create vertices + new_vertex_mappings = {} # current vertex index -> bmesh vertex + for vertex in vertices: + vert = bm.verts.new(vertex.co) + new_vertex_mappings[vertex.index] = vert + + # create faces + for face in faces: + bm.faces.new([new_vertex_mappings[i] for i in face.vertices]) + + bm.normal_update() + bm.verts.ensure_lookup_table() + bm.faces.ensure_lookup_table() + + # create new object based on this mesh data + bpy.ops.mesh.primitive_cube_add() + new_obj = context.object + new_obj.name = "{}.fg".format(src.name) + + # set rotation, translation + new_obj.rotation_euler = src.rotation_euler + new_obj.location = src.location + + # add solidify modifier + bpy.ops.object.modifier_add(type="SOLIDIFY") + new_obj.modifiers["Solidify"].thickness = 6*Units.INCH + new_obj.modifiers["Solidify"].offset = 0 + new_obj.location = src.location + + bm.to_mesh(new_obj.data) + fg.boolean_object = new_obj + bm.free() + + new_obj.hide_viewport = True + + bpy.ops.mesh.primitive_cube_add() + context.object.location = src.location + context.object.jv_properties.convert_source_object = src + src.hide_viewport = True + context.object.jv_properties.object_type_converted = "roofing" # will cause an automatic update + + return {"FINISHED"} + + +# --------------------------------------------------------------------------- +# UIList Handlers +# --------------------------------------------------------------------------- +class OBJECT_UL_face_groups(bpy.types.UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): + layout.label(text="{} face(s)".format(len(item.face_indices.split(",")))) + layout.prop(item, "is_convex", icon="MOD_SIMPLIFY") + + +# --------------------------------------------------------------------------- +# List Operators +# --------------------------------------------------------------------------- +class JVAddCutout(bpy.types.Operator): + bl_idname = "object.jv_add_cutout" + bl_label = "Add Cutout" + bl_description = "JARCH Vis: Add Cutout" + + def execute(self, context): + props = context.object.jv_properties + props.cutouts.add() + bpy.ops.object.jv_update() + + return {"FINISHED"} + + +class JVDeleteCutout(bpy.types.Operator): + bl_idname = "object.jv_delete_cutout" + bl_label = "Delete Cutout" + bl_description = "JARCH Vis: Delete Cutout" + + index: IntProperty(name="Cutout Index") + + def execute(self, context): + props = context.object.jv_properties + + if 0 <= self.index < len(props.cutouts): + props.cutouts.remove(self.index) + bpy.ops.object.jv_update() + + return {"FINISHED"} + + +class JVAddFaceGroup(bpy.types.Operator): + bl_idname = "object.jv_add_face_group" + bl_label = "Add Face Group" + bl_description = "JARCH Vis: Add Face Group" + + def execute(self, context): + props = context.object.jv_properties + fg = props.face_groups.add() + + # make sure face selection gets updated + bpy.ops.object.editmode_toggle() + bpy.ops.object.editmode_toggle() + + indices = [str(face.index) for face in context.object.data.polygons if face.select] + + if not indices: + self.report({"ERROR"}, "At least one face must be selected to add a face group") + else: + fg.face_indices = ",".join(indices) + props.face_groups_index = min(len(props.face_groups) - 1, props.face_groups_index + 1) + + return {"FINISHED"} + + +class JVDeleteFaceGroup(bpy.types.Operator): + bl_idname = "object.jv_delete_face_group" + bl_label = "Delete Face Group" + bl_description = "JARCH Vis: Delete Face Group" + + def execute(self, context): + props = context.object.jv_properties + + if 0 <= props.face_groups_index < len(props.face_groups): + props.face_groups.remove(props.face_groups_index) + props.face_groups_index = max(0, props.face_groups_index-1) + + return {"FINISHED"} + + +classes = ( + JVAddObject, + JVDelete, + JVUpdate, + JVConvert, + + OBJECT_UL_face_groups, + + JVAddCutout, + JVDeleteCutout, + JVAddFaceGroup, + JVDeleteFaceGroup +) + + +def register(): + from bpy.utils import register_class + + for cls in classes: + register_class(cls) + + +def unregister(): + from bpy.utils import unregister_class + + for cls in classes: + unregister_class(cls) + + +if __name__ == "__main__": + register() diff --git a/jv_panel.py b/jv_panel.py new file mode 100644 index 0000000..4829c4c --- /dev/null +++ b/jv_panel.py @@ -0,0 +1,139 @@ +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from bpy.types import Panel +from . jv_types import get_object_type_handler + + +class JVPanel(Panel): + bl_idname = "OBJECT_PT_jv_panel" + bl_label = "JARCH Vis" + bl_space_type = "VIEW_3D" + bl_region_type = "TOOLS" + + jv_add_operators = [ + ("flooring", "Add Flooring", "MESH_GRID"), + ("siding", "Add Siding", "MOD_TRIANGULATE"), + ("roofing", "Add Roofing", "LINCURVE"), + ("windows", "Add Window", "MOD_WIREFRAME") + ] + + jv_consistent_operators = [ + ("object.jv_delete", "CANCEL"), + ] + + def draw(self, context): + layout = self.layout + obj = context.object + + if obj is None or obj.type != "MESH": + # add operators + layout.separator() + box = layout.box() + for tp, label, icon in self.jv_add_operators: + op = box.operator("object.jv_add_object", text=label, icon=icon) + op.object_type = tp + + return + + props = obj.jv_properties + if context.mode == "OBJECT": + if props.object_type == "none" and props.object_type_converted == "none": # convert + layout.operator("object.jv_convert", icon="MOD_EXPLODE") + elif props.object_type != "none" or props.object_type_converted != "none": + converted = props.convert_source_object is not None + + if converted: + layout.prop(props, "object_type_converted", icon="MATERIAL") + else: + layout.prop(props, "object_type", icon="MATERIAL") + layout.separator() + + handler = get_object_type_handler(props.object_type_converted if converted else props.object_type) + if handler is not None: + handler.draw(props, layout) + + # cutouts + if handler.is_cutable: + layout.separator() + box = layout.box() + box.prop(props, "add_cutouts", icon="MOD_BOOLEAN") + + if props.add_cutouts: + for i in range(len(props.cutouts)): + cutout = props.cutouts[i] + + box.separator() + ib = box.box() + + ib.row().prop(cutout, "location") + ib.row().prop(cutout, "dimensions") + ib.row().prop(cutout, "rotation") + ib.prop(cutout, "local", icon="EMPTY_ARROWS") + + op = ib.operator("object.jv_delete_cutout", icon="REMOVE") + op.index = i + + box.separator() + box.operator("object.jv_add_cutout", icon="ADD") + + layout.separator() + box = layout.box() + row = box.row() + row.prop(props, "update_automatically", icon="FILE_REFRESH") + if not props.update_automatically: + row.operator("object.jv_update", icon="FILE_REFRESH") + + box.separator() + for op_name, icon in self.jv_consistent_operators: + box.operator(op_name, icon=icon) + + # add operators + layout.separator() + box = layout.box() + for tp, label, icon in self.jv_add_operators: + op = box.operator("object.jv_add_object", text=label, icon=icon) + op.object_type = tp + + elif context.mode == "EDIT_MESH": + if props.object_type == "none": + row = layout.row(align=True) + row.template_list("OBJECT_UL_face_groups", "", props, "face_groups", props, "face_groups_index", rows=5) + + column = row.column(align=True) + column.operator("object.jv_add_face_group", text="", icon="ADD") + column.operator("object.jv_delete_face_group", text="", icon="REMOVE") + + indices = [] + for fg in props.face_groups: + indices.extend([i for i in fg.face_indices.split(",")]) + + layout.separator() + layout.label(text="{}/{} Faces in Face Groups".format(len(indices), len(context.object.data.polygons)), + icon="INFO") + + +def register(): + from bpy.utils import register_class + + register_class(JVPanel) + + +def unregister(): + from bpy.utils import unregister_class + + unregister_class(JVPanel) + + +if __name__ == "__main__": + register() diff --git a/jv_properties.py b/jv_properties.py index d566f18..7e14151 100644 --- a/jv_properties.py +++ b/jv_properties.py @@ -1,393 +1,694 @@ -# This file is part of JARCH Vis -# -# JARCH Vis is free software: you can redistribute it and/or modify +# This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # -# JARCH Vis is distributed in the hope that it will be useful, +# This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with JARCH Vis. If not, see . - -import bpy -from bpy.props import FloatProperty, IntProperty, EnumProperty, StringProperty, BoolProperty, FloatVectorProperty -from . jv_utils import METRIC_INCH, METRIC_FOOT, delete_materials, preview_materials, update_roofing_facegroup_selection +# along with this program. If not, see . from math import radians +from bpy.types import PropertyGroup, Object +from bpy.props import PointerProperty, EnumProperty, FloatProperty, BoolProperty, IntProperty, FloatVectorProperty, \ + CollectionProperty, StringProperty +from . jv_utils import Units +from . jv_types import get_object_type_handler +import bpy + + +def jv_on_property_update(_, context): + props = context.object.jv_properties + + if props is not None and props.update_automatically: + converted = props.convert_source_object is not None + handler = get_object_type_handler(props.object_type_converted if converted else props.object_type) + handler.update(props, context) + + +def jv_on_face_group_index_update(_, context): + props = context.object.jv_properties + + if 0 <= props.face_groups_index < len(props.face_groups): + indices = set([int(i) for i in props.face_groups[props.face_groups_index].face_indices.split(",")]) + bpy.ops.object.editmode_toggle() + + # deselect everything before selecting the correct faces + for vertex in context.object.data.vertices: + vertex.select = False + + for edge in context.object.data.edges: + edge.select = False + + for face in context.object.data.polygons: + face.select = face.index in indices + + bpy.ops.object.editmode_toggle() + + +class BisectingPlane(PropertyGroup): + normal: FloatVectorProperty( + name="Normal", size=3, unit="LENGTH" + ) + + # LOCAL + center: FloatVectorProperty( + name="Center", size=3, unit="LENGTH" + ) + + +class FaceGroup(PropertyGroup): + face_indices: StringProperty( + name="Face Indices (CSV)", default="" + ) + + is_convex: BoolProperty( + name="Convex?", + description="Are the faces convex? Aka, are all interior angles <= 180 degrees and are there not cutouts?" + ) + + boolean_object: PointerProperty( + name="Bolean Object", type=Object + ) + + # the rotation of the face group from the X-Y plane + rotation: FloatVectorProperty( + subtype="EULER", size=3 + ) + + # LOCAL coordinate of bottom-left corner + location: FloatVectorProperty( + subtype="TRANSLATION", size=3 + ) + + dimensions: FloatVectorProperty( + unit="LENGTH", size=2 + ) + + bisecting_planes: CollectionProperty( + name="Bisecting Planes", type=BisectingPlane + ) + + +class Cutout(PropertyGroup): + location: FloatVectorProperty( + name="Location", + default=(0.0, 0.0, 0.0), step=1, precision=3, subtype="TRANSLATION", size=3, + description="The position of the lower-bottom-left corner", update=jv_on_property_update + ) + + rotation: FloatVectorProperty( + name="Rotation", + default=(0.0, 0.0, 0.0), step=2, precision=3, subtype="EULER", size=3, + description="The rotation of the cutout", update=jv_on_property_update + ) + + dimensions: FloatVectorProperty( + name="Dimensions", + default=(Units.FOOT, Units.FOOT, Units.FOOT), step=1, precision=3, unit="LENGTH", size=3, min=0.0, + description="The the dimensions of the cutout", update=jv_on_property_update + ) + + local: BoolProperty( + name="Local Coordinates?", default=True, + description="Are offset and rotation values in reference to the object's origin?", + update=jv_on_property_update + ) + + +class JVProperties(PropertyGroup): + object_type: EnumProperty( + name="Type", + items=( + ("none", "None", ""), + ("flooring", "Flooring", ""), + ("siding", "Siding", ""), + ("roofing", "Roofing", ""), + ("windows", "Windows", "") + ), + default="none", description="The type of architecture to build", update=jv_on_property_update + ) + + object_type_converted: EnumProperty( + name="Type", + items=( + ("none", "None", ""), + ("flooring", "Flooring", ""), + ("siding", "Siding", ""), + ("roofing", "Roofing", "") + ), + default="none", description="The type of architecture to build", update=jv_on_property_update + ) + + update_automatically: BoolProperty( + name="Update Automatically?", + default=True, description="Update the mesh anytime a property is changed?", update=jv_on_property_update + ) + + convert_source_object: PointerProperty( + name="Convert Source Object", type=Object + ) + + # OBJECT STYLES ------------------------------------------------------------------------------ + flooring_pattern: EnumProperty( + name="Pattern", + items=( + ("regular", "Regular", ""), # wood-like + ("checkerboard", "Checkerboard", ""), # wood-like + ("herringbone", "Herringbone", ""), # wood-like + ("chevron", "Chevron", ""), # wood-like + ("hopscotch", "Hopscotch", ""), # tile-like + ("windmill", "Windmill", ""), # tile-like + ("stepping_stone", "Stepping Stone", ""), # tile-like + ("hexagons", "Hexagons", ""), # tile-like + ("octagons", "Octagons", ""), # tile-like + ("corridor", "Cooridor", "") # tile-like + ), default="regular", description="Flooring Pattern", update=jv_on_property_update + ) + + siding_pattern: EnumProperty( + name="Pattern", + items=( + ("regular", "Regular", ""), + ("dutch_lap", "Dutch Lap", ""), + ("shiplap", "Shiplap", ""), + ("clapboard", "Clapboard", ""), + ("tin_regular", "Tin - Regular", ""), + ("tin_angular", "Tin - Angular", ""), + ("brick", "Brick", ""), + ("shakes", "Shakes", ""), + ("scallop_shakes", "Scallop Shakes", "") + ), default="regular", description="Siding Pattern", update=jv_on_property_update + ) + + roofing_pattern: EnumProperty( + name="Pattern", + items=( + ("tin_regular", "Tin - Regular", ""), + ("tin_angular", "Tin - Angular", ""), + ("tin_standing_seam", "Tin - Standing Seam", ""), + ("shingles_3_tab", "Shingles - 3 Tab", ""), + ("shingles_architectural", "Shingles - Architectural", ""), + ("shakes", "Shakes", ""), + ("terracotta", "Terracotta", "") + ), default="tin_regular", description="Roofing Pattern", update=jv_on_property_update + ) + + window_pattern: EnumProperty( + name="Pattern", + items=( + ("regular", "Regular", ""), + ("arch", "Arch", ""), + ("polygon", "Polygon", ""), + ("gothic", "Gothic", ""), + ("ellipse", "Ellipse", ""), + ("circular", "Circular", ""), + ("bow", "Bow", ""), + ("bay", "Bay", "") + ), default="regular", description="Window Pattern", update=jv_on_property_update + ) + + # OVERALL DIMENSIONS ------------------------------------------------------------------------ + length: FloatProperty( + name="Total Length", + min=0.5 * Units.FOOT, default=20 * Units.FOOT, precision=4, + subtype="DISTANCE", description="Total length of material", update=jv_on_property_update + ) + + width: FloatProperty( + name="Total Width", + min=0.5 * Units.FOOT, default=8 * Units.FOOT, precision=4, + subtype="DISTANCE", description="Total width of material", update=jv_on_property_update + ) + + height: FloatProperty( + name="Total Height", + min=1 * Units.FOOT, default=8 * Units.FOOT, precision=3, subtype="DISTANCE", + description="Total height of the material", update=jv_on_property_update + ) + + # CUTOUTS ----------------------------------------------------------------------------------- + add_cutouts: BoolProperty( + name="Cutouts?", + description="Add box cutouts for things like windows or doors?", update=jv_on_property_update + ) + + cutouts: CollectionProperty( + name="Cutouts", type=Cutout + ) + + # OBJECT STYLES ------------------------------------------------------------------------------ + face_groups: CollectionProperty( + name="Face Groups", type=FaceGroup + ) + + face_groups_index: IntProperty( + name="Face Groups Index", update=jv_on_face_group_index_update + ) + + # MATERIAL DIMENSIONS ----------------------------------------------------------------------- + board_width_wide: FloatProperty( + name="Board Width", + min=1 * Units.INCH, default=8 * Units.INCH, + subtype="DISTANCE", description="The width of each board", update=jv_on_property_update + ) + + board_width_medium: FloatProperty( + name="Board Width", + min=1 * Units.INCH, default=6 * Units.INCH, + subtype="DISTANCE", description="The width of each board", update=jv_on_property_update + ) + + board_width_narrow: FloatProperty( + name="Board Width", + min=1 * Units.INCH, default=3 * Units.INCH, + subtype="DISTANCE", description="The width of each board", update=jv_on_property_update + ) + + vary_width: BoolProperty( + name="Vary Width?", + default=False, description="Vary the width of each board?", update=jv_on_property_update + ) + + width_variance: FloatProperty( + name="Width Variance", + min=1.00, max=100.00, default=25.00, subtype="PERCENTAGE", + description="The width of each board will be in width +- width*variance", update=jv_on_property_update + ) + + board_length_long: FloatProperty( + name="Board Length", + min=1 * Units.FOOT, default=8 * Units.FOOT, precision=4, + subtype="DISTANCE", description="The length of each board", update=jv_on_property_update + ) + + board_length_medium: FloatProperty( + name="Board Length", + min=1 * Units.FOOT, default=4 * Units.FOOT, precision=4, + subtype="DISTANCE", description="The length of each board", update=jv_on_property_update + ) + + board_length_short: FloatProperty( + name="Board Length", + min=1 * Units.FOOT, default=2 * Units.FOOT, precision=4, + subtype="DISTANCE", description="The length of each board", update=jv_on_property_update + ) + + board_length_really_short: FloatProperty( + name="Board Length", + min=6 * Units.INCH, default=1 * Units.FOOT, precision=4, + subtype="DISTANCE", description="The length of each board", update=jv_on_property_update + ) + + vary_length: BoolProperty( + name="Vary Length?", + default=False, description="Vary the length of each board?", update=jv_on_property_update + ) + + length_variance: FloatProperty( + name="Length Variance", + min=1.00, max=100.00, default=25.00, subtype="PERCENTAGE", + description="The length of each board will be in length +- length*variance", update=jv_on_property_update + ) + + thickness_thin: FloatProperty( + name="Thickness", + min=Units.ETH_INCH, default=5*Units.STH_INCH, step=1, precision=4, subtype="DISTANCE", + description="The thickness of each board, tile, shingle, etc.", update=jv_on_property_update + ) + + thickness: FloatProperty( + name="Thickness", + min=Units.ETH_INCH, default=1.5 * Units.INCH, step=1, subtype="DISTANCE", + description="The thickness of each board, tile, shingle, etc.", update=jv_on_property_update + ) + + thickness_thick: FloatProperty( + name="Thickness", + min=1*Units.ETH_INCH, default=2.5*Units.INCH, step=1, precision=3, subtype="DISTANCE", + description="The thickness of each board, tile, shingle, etc.", update=jv_on_property_update + ) + + vary_thickness: BoolProperty( + name="Vary Thickness?", + default=False, description="Vary the thickness of each board?", update=jv_on_property_update + ) + + thickness_variance: FloatProperty( + name="Thickness Variance", + min=1.00, max=100.00, default=25.00, subtype="PERCENTAGE", + description="The thickness of each board will be in thickness +- thickness*variance", + update=jv_on_property_update + ) + + gap_uniform: FloatProperty( + name="Gap", + min=0.00, default=1 * Units.STH_INCH, subtype="DISTANCE", step=1, precision=5, + description="The gap around each board or tile", update=jv_on_property_update + ) + + gap_widthwise: FloatProperty( + name="Gap - Widthwise", + min=0.00, default=Units.ETH_INCH, subtype="DISTANCE", step=1, precision=4, + description="The gap between the board or tile in the width direction", update=jv_on_property_update + ) + + gap_lengthwise: FloatProperty( + name="Gap - Lengthwise", + min=0.00, default=Units.STH_INCH, subtype="DISTANCE", step=1, precision=4, + description="The gap between the board or tile in the length direction", update=jv_on_property_update + ) + + row_offset: FloatProperty( + name="Row Offset", + min=0.00, max=100.00, default=50.00, subtype="PERCENTAGE", + description="How much alternating rows are offset", update=jv_on_property_update + ) + + vary_row_offset: BoolProperty( + name="Vary Row Offset?", + default=False, description="Vary the offset of each row?", update=jv_on_property_update + ) + + row_offset_variance: FloatProperty( + name="Row Offset Variance", + min=0.00, max=100.00, default=50.00, subtype="PERCENTAGE", + description="Each row will be offset between (width / 2) * (1 - variance)", update=jv_on_property_update + ) + + add_grout: BoolProperty( + name="Add Grout?", + default=True, description="Add cube for where the grout/mortar would be?", update=jv_on_property_update + ) + + grout_depth: FloatProperty( + name="Grout Depth", + min=0.00, max=100.00, default=5.00, precision=4, step=5, subtype="PERCENTAGE", + description="The depth of the grout is depth*thickness", update=jv_on_property_update + ) + + shake_width: FloatProperty( + name="Shake Width", + min=1*Units.INCH, default=4*Units.INCH, precision=4, step=1, subtype="DISTANCE", + description="The width of each shake", update=jv_on_property_update + ) + + shake_length: FloatProperty( + name="Shake Length", + min=1*Units.INCH, default=6*Units.INCH, precision=4, step=2, subtype="DISTANCE", + description="The length of each shake, the actual exposure will be length/2", update=jv_on_property_update + ) + + scallop_resolution: IntProperty( + name="Curve Resolution", + min=1, default=8, description="The smoothness of the curve", update=jv_on_property_update + ) + + pitch: FloatProperty( + name="Pitch X/12", + default=4.00, min=0.00, step=1, precision=3, + description="Pitch/Slope of the top of the siding, in rise/run format that is x/12", + update=jv_on_property_update + ) + + orientation: EnumProperty( + name="Orientation", + items=( + ("vertical", "Vertical", ""), + ("horizontal", "Horizontal", "") + ), default="vertical", description="Orientation", update=jv_on_property_update + ) + + # FLOORING SPECIFIC ------------------------------------------------------------------------- + tile_width: FloatProperty( + name="Tile Width", + min=1 * Units.INCH, default=8 * Units.INCH, subtype="DISTANCE", + description="The width of each tile", update=jv_on_property_update + ) + + tile_length: FloatProperty( + name="Tile Length", + min=1 * Units.INCH, default=8 * Units.INCH, subtype="DISTANCE", + description="The length of each tile", update=jv_on_property_update + ) + + checkerboard_board_count: IntProperty( + name="Boards in Square", + min=1, default=4, description="Number of boards in each square of checkerboard pattern", + update=jv_on_property_update + ) + + with_dots: BoolProperty( + name="With Dots?", + default=True, description="Add cube between corners of hexagons?", update=jv_on_property_update + ) + + side_length: FloatProperty( + name="Polygon Side Length", + min=1 * Units.INCH, default=4 * Units.INCH, precision=4, step=2, subtype="DISTANCE", + description="The length of each side in the regular polygon", update=jv_on_property_update + ) + + alternating_row_width: FloatProperty( + name="Alternating Row Width", + min=1 * Units.INCH, default=3 * Units.INCH, precision=4, step=2, subtype="DISTANCE", + description="The width of the tiles in the alternating rows", update=jv_on_property_update + ) + + # SIDING SPECIFIC ------------------------------------------------------------------------- + slope_top: BoolProperty( + name="Slope Top?", + default=False, description="Cut a slope on the top of the siding?", update=jv_on_property_update + ) + + pitch_offset: FloatVectorProperty( + name="Offset of Slope", + default=(0.0, 0.0, 0.0), size=3, precision=3, subtype="TRANSLATION", + description="Offset from the top-center of the siding for the slope", update=jv_on_property_update + ) + + dutch_lap_breakpoint: FloatProperty( + name="Slope Breakpoint", + default=65.00, min=5.00, max=95.00, precision=2, subtype="PERCENTAGE", + description="The board will start sloping back at width*breakpoint up from the bottom", + update=jv_on_property_update + ) + + battens: BoolProperty( + name="Add Battens?", + default=False, description="Add battens to the siding to cover the gaps between boards?", + update=jv_on_property_update + ) + + batten_width: FloatProperty( + name="Batten Width", + default=2*Units.INCH, min=0.5*Units.INCH, subtype="DISTANCE", + description="The width of the battens", update=jv_on_property_update + ) + + vary_batten_width: BoolProperty( + name="Vary Batten Width?", + default=False, description="Vary batten width?", update=jv_on_property_update + ) + + batten_width_variance: FloatProperty( + name="Batten Width Variance", + min=0.00, max=100.00, default=50.00, subtype="PERCENTAGE", + description="Each batten's width will be in width +- (w * variance)", update=jv_on_property_update + ) + + brick_height: FloatProperty( + name="Brick Width", + min=1*Units.INCH, default=9*Units.Q_INCH, precision=4, step=1, subtype="DISTANCE", + description="The height of each brick", update=jv_on_property_update + ) + + brick_length: FloatProperty( + name="Brick Length", + min=1*Units.INCH, default=8*Units.INCH, precision=4, step=1, subtype="DISTANCE", + description="The length of each brick", update=jv_on_property_update + ) + + joint_left: BoolProperty( + name="Joint Left?", + default=False, description="Leave 'thickness + gap' overhang on odd rows to allow jointing?", + update=jv_on_property_update + ) + + joint_right: BoolProperty( + name="Joint Right?", + default=False, description="Leave 'thickness + gap' overhang on even rows to allow jointing?", + update=jv_on_property_update + ) + + # ROOFING SPECIFIC ------------------------------------------------------------------------- + pan_width: FloatProperty( + name="Pan Width", + min=1*Units.INCH, default=Units.FOOT, precision=4, step=1, subtype="DISTANCE", + description="The width of each pan", update=jv_on_property_update + ) + + terracotta_resolution: IntProperty( + name="Curve Resolution", + min=1, default=12, description="The resolution of the curve on the tile", update=jv_on_property_update + ) + + terracotta_radius: FloatProperty( + name="Tile Radius", + min=Units.INCH, default=2*Units.INCH, precision=4, step=1, subtype="DISTANCE", + description="The radius of the half-circle on the tile", update=jv_on_property_update + ) + + terracotta_gap: FloatProperty( + name="Tile Gap", + min=Units.H_INCH, default=1.5*Units.INCH, precision=4, step=1, subtype="DISTANCE", + description="The distance between the half-circles on the tiles", update=jv_on_property_update + ) + + mirror: BoolProperty( + name="Mirror?", default=False, description="Mirror roofing across X-axis?", update=jv_on_property_update + ) + + # WINDOW SPECIFIC -------------------------------------------------------------------------- + jamb_width: FloatProperty( + name="Jamb Width", + min=1 * Units.INCH, default=4 * Units.INCH, subtype="DISTANCE", + description="The width of the jamb", update=jv_on_property_update + ) + + frame_width: FloatProperty( + name="Frame Width", + min=Units.ETH_INCH, default=1.5 * Units.INCH, subtype="DISTANCE", + description="The width of the frame around the glass", update=jv_on_property_update + ) + + frame_thickness: FloatProperty( + name="Frame Thickness", + min=Units.H_INCH, default=Units.INCH, step=1, precision=3, subtype="DISTANCE", + description="The thickness of the frame surrounding the glass pane", update=jv_on_property_update + ) + + window_width_medium: FloatProperty( + name="Width", + min=Units.FOOT, default=32 * Units.INCH, subtype="DISTANCE", + description="The width of the windows", update=jv_on_property_update + ) + + window_width_wide: FloatProperty( + name="Width", + min=Units.FOOT, default=60 * Units.INCH, subtype="DISTANCE", + description="The width of the windows", update=jv_on_property_update + ) + + window_width_extra_wide: FloatProperty( + name="Width", + min=Units.FOOT, default=6 * Units.FOOT, subtype="DISTANCE", + description="The width of the windows", update=jv_on_property_update + ) + + window_height_tall: FloatProperty( + name="Height", + min=Units.FOOT, default=6 * Units.FOOT, subtype="DISTANCE", + description="The height of the windows", update=jv_on_property_update + ) + + window_height_medium: FloatProperty( + name="Height", + min=Units.FOOT, default=4 * Units.FOOT, subtype="DISTANCE", + description="The height of the gliding windows", update=jv_on_property_update + ) + + window_height_short: FloatProperty( + name="Height", + min=Units.FOOT, default=3 * Units.FOOT, subtype="DISTANCE", + description="The height of the gliding windows", update=jv_on_property_update + ) + + num_joined_windows: IntProperty( + name="Window Gang Count", + min=1, default=1, update=jv_on_property_update + ) + + window_radius: FloatProperty( + name="Radius", + min=Units.FOOT, default=1.5 * Units.FOOT, subtype="DISTANCE", + description="The radius of the window", update=jv_on_property_update + ) + + window_side_count: IntProperty( + name="Sides", + min=3, default=3, update=jv_on_property_update + ) + + full_circle: BoolProperty( + name="Full Circle?", + default=True, update=jv_on_property_update + ) + + window_angle: FloatProperty( + name="Angle", + unit="ROTATION", min=radians(15), default=radians(90), update=jv_on_property_update + ) + + window_roundness: FloatProperty( + name="Roundness", + max=100.0, min=1.0, default=25.0, subtype="PERCENTAGE", update=jv_on_property_update, + description="The ellipse's height will be (width / 2) * roundness. A value of 100% will form a half-circle" + ) + + window_resolution: IntProperty( + name="Resolution", + min=10, default=64, step=2, update=jv_on_property_update + ) + + slider: BoolProperty( + name="Slider?", + default=True, update=jv_on_property_update + ) + + bay_angle: FloatProperty( + name="Side Pane Angle", + min=radians(10), max=radians(90), default=radians(45), subtype="ANGLE", update=jv_on_property_update + ) + + window_depth: FloatProperty( + name="Window Depth", + min=Units.FOOT, default=2 * Units.FOOT, subtype="DISTANCE", update=jv_on_property_update + ) + + bow_segments: IntProperty( + name="Segments", + min=2, default=5, update=jv_on_property_update + ) + + +def register(): + from bpy.utils import register_class + from bpy.types import Object + + register_class(BisectingPlane) + register_class(Cutout) + register_class(FaceGroup) + register_class(JVProperties) + Object.jv_properties = PointerProperty( + type=JVProperties, + name="jv_properties", + description="All possible properties for any JARCH Vis object" + ) + + +def unregister(): + from bpy.utils import unregister_class + from bpy.types import Object + + del Object.jv_properties + unregister_class(JVProperties) + unregister_class(FaceGroup) + unregister_class(Cutout) + unregister_class(BisectingPlane) -def jv_update_object(self, context): - from . jv_flooring import update_flooring - from . jv_roofing import update_roofing - from . jv_siding import update_siding - from . jv_stairs import update_stairs - from . jv_windows import update_window - - o = context.object - updates = {"flooring": update_flooring, "siding": update_siding, "roofing": update_roofing, "stair": update_stairs, - "window": update_window} - - if o.jv_internal_type in updates: - updates[o.jv_internal_type](self, context) - -tp = bpy.types.Object -tp.jv_internal_type = StringProperty() -tp.jv_object_add = StringProperty(default="none", update=jv_update_object) -tp.jv_cut_name = StringProperty(default="none") -tp.jv_is_cut = StringProperty(default="none") - -# types/styles -tp.jv_w_types = EnumProperty(items=(("1", "Double-Hung", ""), ("2", "Gliding", ""), ("3", "Stationary", ""), - ("4", "Odd-Shaped", ""), ("5", "Bay/Bow", "")), name="", update=jv_update_object) -tp.jv_odd_types = EnumProperty(items=(("1", "Polygon", ""), ("2", "Circular", ""), ("3", "Arch", ""), - ("4", "Gothic", ""), ("5", "Oval", "")), name="", update=jv_update_object) -tp.jv_flooring_types = EnumProperty(items=(("1", "Wood", ""), ("2", "Tile", "")), default="1", description="Material", - update=jv_update_object, name="") -tp.jv_wood_flooring_types = EnumProperty(items=(("1", "Regular", ""), ("2", "Parquet", ""), - ("3", "Herringbone Parquet", ""), ("4", "Herringbone", "")), - default="1", description="Wood Type", update=jv_update_object, name="") -tp.jv_tile_types = EnumProperty(items=(("1", "Regular", ""), ("2", "Large + Small", ""), - ("3", "Large + Many Small", ""), ("4", "Hexagonal", "")), default="1", - description="Tile Type", update=jv_update_object, name="") -tp.jv_roofing_types = EnumProperty(name="Material", items=(("1", "Tin", ""), ("2", "Shingles", ""), - ("3", "Terra Cotta", "")), update=jv_update_object) -tp.jv_shingle_types = EnumProperty(name="Shingle Style", items=(("1", "Architectural", ""), ("2", "3-Tab", "")), - update=jv_update_object) -tp.jv_tin_roofing_types = EnumProperty(name="Tin Type", items=(("1", "Normal", ""), ("2", "Angular", ""), - ("3", "Standing Seam", "")), update=jv_update_object) -tp.jv_stair_style = EnumProperty(items=(("1", "Normal", ""), ("2", "Winding", ""), ("3", "Spiral", "")), default="1", - description="Stair Style", update=jv_update_object, name="") -tp.jv_siding_types = EnumProperty(items=(("1", "Wood", ""), ("2", "Vinyl", ""), ("3", "Tin", ""), - ("4", "Fiber Cement", ""), ("5", "Bricks", ""), ("6", "Stone", "")), - default="1", name="", update=jv_update_object) -tp.jv_tin_siding_types = EnumProperty(items=(("1", "Normal", ""), ("2", "Angular", "")), default="1", name="", - update=jv_update_object) -tp.jv_wood_siding_types = EnumProperty(items=(("1", "Vertical", ""), ("2", "Vertical: Tongue & Groove", ""), - ("3", "Vertical: Board & Batten", ""), ("4", "Horizontal: Lap", ""), - ("5", "Horizontal: Lap Bevel", "")), default="1", name="", - update=jv_update_object) -tp.jv_vinyl_siding_types = EnumProperty(items=(("1", "Vertical", ""), ("2", "Horizontal: Lap", ""), - ("3", "Horizontal: Dutch Lap", "")), default="1", name="", - update=jv_update_object) - -# shared measurements -tp.jv_over_width = FloatProperty(name="Overall Width", min=2.00 / METRIC_FOOT, max=100.00 / METRIC_FOOT, - default=20 / METRIC_FOOT, subtype="DISTANCE", description="Overall Width", - update=jv_update_object) -tp.jv_over_length = FloatProperty(name="Overall Length", min=2.0 / METRIC_FOOT, max=100.00 / METRIC_FOOT, - default=8 / METRIC_FOOT, subtype="DISTANCE", description="Overall Length", - update=jv_update_object) -tp.jv_b_width = FloatProperty(name="Board Width", min=2.00 / METRIC_INCH, max=14.00 / METRIC_INCH, - default=6.00 / METRIC_INCH, subtype="DISTANCE", description="Board Width", - update=jv_update_object) -tp.jv_b_length = FloatProperty(name="Board Length", min=4.00 / METRIC_FOOT, max=20.00 / METRIC_FOOT, - default=8.00 / METRIC_FOOT, subtype="DISTANCE", description="Board Length", - update=jv_update_object) -tp.jv_is_length_vary = BoolProperty(name="Vary Length?", default=False, description="Vary Lengths?", - update=jv_update_object) -tp.jv_length_vary = FloatProperty(name="Length Variance", min=1.00, max=100.0, default=50.0, subtype="PERCENTAGE", - description="Length Variance", update=jv_update_object) -tp.jv_max_boards = IntProperty(name="Max # Of Boards", min=2, max=10, default=2, - description="Maximum Number Of Boards Possible In One Length", update=jv_update_object) -tp.jv_is_width_vary = BoolProperty(name="Vary Width?", default=False, description="Vary Widths?", - update=jv_update_object) -tp.jv_width_vary = FloatProperty(name="Width Variance", min=1.00, max=100.0, default=50.0, subtype="PERCENTAGE", - description="Width Variance", update=jv_update_object) -tp.jv_slope = FloatProperty(name="Slope (X/12)", min=1.0, max=12.0, default=4.0, - description="Slope In RISE/RUN Format In Inches", update=jv_update_object) - -# shared materials -tp.jv_is_material = BoolProperty(name="Cycles Materials?", default=False, description="Adds Cycles Materials", - update=delete_materials) -tp.jv_is_preview = BoolProperty(name="Preview Material?", default=False, description="Preview Material On Object", - update=preview_materials) -tp.jv_im_scale = FloatProperty(name="Image Scale", max=10.0, min=0.1, default=1.0, description="Change Image Scaling") -tp.jv_col_image = StringProperty(name="", subtype="FILE_PATH", description="File Path For Color Image") -tp.jv_is_bump = BoolProperty(name="Normal Map?", default=False, description="Add Normal To Material?") -tp.jv_norm_image = StringProperty(name="", subtype="FILE_PATH", description="File Path For Normal Map Image") -tp.jv_bump_amo = FloatProperty(name="Normal Strength", min=0.001, max=2.000, default=0.250, - description="Normal Map Strength") -tp.jv_is_unwrap = BoolProperty(name="UV Unwrap?", default=True, description="UV Unwraps Siding", - update=jv_update_object) -tp.jv_is_random_uv = BoolProperty(name="Random UV's?", default=True, description="Random UV's", update=jv_update_object) -tp.jv_mortar_color = FloatVectorProperty(name="Mortar Color", subtype="COLOR", default=(1.0, 1.0, 1.0), min=0.0, - max=1.0, description="Color For Mortar") -tp.jv_mortar_bump = FloatProperty(name="Mortar Bump", min=0.0, max=1.0, default=0.25, description="Mortar Bump Amount") -tp.jv_is_rotate = BoolProperty(name="Rotate Image?", default=False, description="Rotate Image 90 Degrees") -tp.jv_rgba_color = FloatVectorProperty(name="Color", subtype="COLOR", default=(1.0, 1.0, 1.0), min=0.0, max=1.0, - description="Color For Material") -tp.jv_color_image = EnumProperty(name="Material Type", items=(("rgba", "RGBA", ""), ("image", "Image", ""))) - -# window specific -tp.jv_jamb_width = FloatProperty(name="Jamb Width", subtype="DISTANCE", min=2 / METRIC_INCH, max=8 / METRIC_INCH, - default=4 / METRIC_INCH, update=jv_update_object) -tp.jv_dh_width = FloatProperty(name="Width", subtype="DISTANCE", min=1 / METRIC_FOOT, max=3 / METRIC_FOOT, - default=32 / METRIC_INCH, update=jv_update_object) -tp.jv_dh_height = FloatProperty(name="Height", subtype="DISTANCE", min=1 / METRIC_FOOT, max=6 / METRIC_FOOT, - default=48 / METRIC_INCH, update=jv_update_object) -tp.jv_gang_num = IntProperty(name="Number Ganged Together", min=1, max=4, default=1, update=jv_update_object) -tp.jv_gl_width = FloatProperty(name="Width", subtype="DISTANCE", min=1 / METRIC_FOOT, max=6 / METRIC_FOOT, - default=60 / METRIC_INCH, update=jv_update_object) -tp.jv_gl_height = FloatProperty(name="Height", subtype="DISTANCE", min=1 / METRIC_FOOT, max=4 / METRIC_FOOT, - default=36 / METRIC_INCH, update=jv_update_object) -tp.jv_gl_slide_right = BoolProperty(name="Slide Right?", default=True, update=jv_update_object) -tp.jv_so_width = FloatProperty(name="Width", subtype="DISTANCE", min=1 / METRIC_FOOT, max=6 / METRIC_FOOT, - default=24 / METRIC_INCH, update=jv_update_object) -tp.jv_so_height = FloatProperty(name="Height", subtype="DISTANCE", min=1 / METRIC_FOOT, max=10 / METRIC_FOOT, - default=36 / METRIC_INCH, update=jv_update_object) -tp.jv_so_height_tall = FloatProperty(name="Height", subtype="DISTANCE", min=3 / METRIC_FOOT, max=20 / METRIC_FOOT, - default=5 / METRIC_FOOT, update=jv_update_object) -tp.jv_o_radius = FloatProperty(name="Radius", subtype="DISTANCE", min=1 / METRIC_FOOT, max=3 / METRIC_FOOT, - default=1.5 / METRIC_FOOT, update=jv_update_object) -tp.jv_sides = IntProperty(name="Sides", min=3, max=12, default=3, update=jv_update_object) -tp.jv_full_circle = BoolProperty(name="Full Circle?", default=True, update=jv_update_object) -tp.jv_w_angle = FloatProperty(name="Angle", unit="ROTATION", min=radians(45), max=radians(270), default=radians(90), - update=jv_update_object) -tp.jv_roundness = FloatProperty(name="Roundness", subtype="PERCENTAGE", max=100.0, min=1.0, default=25.0, - update=jv_update_object) -tp.jv_resolution = IntProperty(name="Resolution", min=32, max=512, default=64, step=2, update=jv_update_object) -tp.jv_is_slider = BoolProperty(name="Slider?", update=jv_update_object) -tp.jv_ba_width = FloatProperty(name="Width", subtype="DISTANCE", min=2 / METRIC_FOOT, max=20 / METRIC_FOOT, - default=72 / METRIC_INCH, update=jv_update_object) -tp.jv_ba_height = FloatProperty(name="Height", subtype="DISTANCE", min=1 / METRIC_FOOT, max=15 / METRIC_FOOT, - default=48 / METRIC_INCH, update=jv_update_object) -tp.jv_is_bay = BoolProperty(name="Bay?", default=True, update=jv_update_object) -tp.jv_bay_angle = FloatProperty(name="Side Pane Angle", subtype="ANGLE", min=radians(10), max=radians(75), - default=radians(45), update=jv_update_object) -tp.jv_depth = FloatProperty(name="Window Depth", subtype="DISTANCE", min=12/METRIC_INCH, max=4/METRIC_FOOT, - default=2/METRIC_FOOT, update=jv_update_object) -tp.jv_bow_segments = EnumProperty(items=(("2", "2", ""), ("4", "4", ""), ("6", "6", ""), ("8", "8", ""), - ("10", "10", ""), ("12", "12", ""), ("14", "14", "")), name="Segments", - update=jv_update_object) -tp.jv_is_split_center = BoolProperty(name="Split Center Pane?", default=False, update=jv_update_object) -tp.jv_is_double_hung = BoolProperty(name="Double Hung?", default=True, update=jv_update_object) - -# flooring specific -tp.jv_b_length_s = FloatProperty(name="Board Length", min=1.00 / METRIC_FOOT, max=4.00 / METRIC_FOOT, - default=1.5 / METRIC_FOOT, subtype="DISTANCE", description="Board Length", - update=jv_update_object) -tp.jv_hb_direction = EnumProperty(items=(("1", "Forwards (+y)", ""), ("2", "Backwards (-y)", ""), - ("3", "Right (+x)", ""), ("4", "Left (-x)", "")), name="Direction", - description="Herringbone Direction", update=jv_update_object) -tp.jv_thickness = FloatProperty(name="Floor Thickness", min=0.75 / METRIC_INCH, max=1.5 / METRIC_INCH, - default=1 / METRIC_INCH, subtype="DISTANCE", description="Thickness Of Flooring", - update=jv_update_object) -tp.jv_num_boards = IntProperty(name="# Of Boards", min=2, max=6, default=4, description="Number Of Boards In Square", - update=jv_update_object) -tp.jv_space_l = FloatProperty(name="Length Spacing", min=0.001 / METRIC_INCH, max=0.5 / METRIC_INCH, - default=0.125 / METRIC_INCH, subtype="DISTANCE", - description="Space Between Boards Length Ways", update=jv_update_object) -tp.jv_space_w = FloatProperty(name="Width Spacing", min=0.001 / METRIC_INCH, max=0.5 / METRIC_INCH, - default=0.125 / METRIC_INCH, subtype="DISTANCE", - description="Space Between Boards Width Ways", update=jv_update_object) -tp.jv_spacing = FloatProperty(name="Spacing", min=0.001 / METRIC_INCH, max=1.0 / METRIC_INCH, - default=0.25 / METRIC_INCH, subtype="DISTANCE", description="Space Between Tiles/Boards", - update=jv_update_object) -tp.jv_is_bevel = BoolProperty(name="Bevel?", default=False, update=jv_update_object) -tp.jv_bevel_res = IntProperty(name="Bevel Resolution", min=1, max=10, default=1, update=jv_update_object) -tp.jv_bevel_amo = FloatProperty(name="Bevel Amount", min=0.001 / METRIC_INCH, max=0.5 / METRIC_INCH, - default=0.15 / METRIC_INCH, subtype="DISTANCE", description="Bevel Amount", - update=jv_update_object) -tp.jv_is_ran_thickness = BoolProperty(name="Random Thickness?", default=False, update=jv_update_object) -tp.jv_ran_thickness = FloatProperty(name="Thickness Variance", min=0.1, max=100.0, default=50.0, subtype="PERCENTAGE", - update=jv_update_object) -tp.jv_t_width = FloatProperty(name="Tile Width", min=2.00 / METRIC_INCH, max=24.00 / METRIC_INCH, - default=8.00 / METRIC_INCH, subtype="DISTANCE", description="Tile Width", - update=jv_update_object) -tp.jv_t_length = FloatProperty(name="Tile Length", min=2.00 / METRIC_INCH, max=24.00 / METRIC_INCH, - default=8.00 / METRIC_INCH, subtype="DISTANCE", description="Tile Length", - update=jv_update_object) -tp.jv_grout_depth = FloatProperty(name="Grout Depth", min=0.01 / METRIC_INCH, max=0.40 / 39.701, - default=0.10 / METRIC_INCH, subtype="DISTANCE", description="Grout Depth", - update=jv_update_object) -tp.jv_is_offset = BoolProperty(name="Offset Tiles?", default=False, description="Offset Tile Rows", - update=jv_update_object) -tp.jv_offset = FloatProperty(name="Offset", min=0.001, max=100.0, default=50.0, subtype="PERCENTAGE", - description="Tile Offset Amount", update=jv_update_object) -tp.jv_is_random_offset = BoolProperty(name="Random Offset?", default=False, description="Offset Tile Rows Randomly", - update=jv_update_object) -tp.jv_offset_vary = FloatProperty(name="Offset Variance", min=0.001, max=100.0, default=50.0, subtype="PERCENTAGE", - description="Offset Variance", update=jv_update_object) -tp.jv_t_width_s = FloatProperty(name="Small Tile Width", min=2.00 / METRIC_INCH, max=10.00 / METRIC_INCH, - default=6.00 / METRIC_INCH, subtype="DISTANCE", description="Small Tile Width", - update=jv_update_object) - -# roofing specific -tp.jv_face_group_ct = IntProperty(default=0) -tp.jv_face_group_index = IntProperty(default=0, update=update_roofing_facegroup_selection) -tp.jv_pl_z_rot = FloatProperty(unit="ROTATION", name="Object Z Rotation") -tp.jv_pl_pitch = FloatProperty(min=1.0, max=24.0, default=4.0, name="Pitch X/12") -tp.jv_main_name = StringProperty(default="none") -tp.jv_is_mirrored = BoolProperty(name="Mirror?", default=True, update=jv_update_object) - -tp.jv_terra_cotta_res = IntProperty(name="Curve Resolution", min=3, max=10, default=5, update=jv_update_object) -tp.jv_tile_radius = FloatProperty(name="Tile Radius", min=1.5 / METRIC_INCH, max=3.0 / METRIC_INCH, - default=2.0 / METRIC_INCH, update=jv_update_object, subtype="DISTANCE") -tp.jv_tin_color = FloatVectorProperty(name="Tin Color", subtype="COLOR", default=(1.0, 1.0, 1.0), min=0.0, max=1.0, - description="Color For Tin") - -# siding specific -# cutout groups -tp.jv_cutout_group_ct = IntProperty(default=0) -tp.jv_cutout_group_index = IntProperty(default=0) -tp.jv_cutout_x = FloatProperty(name="X Distance", subtype="DISTANCE", description="X Distance From Left Side") -tp.jv_cutout_z = FloatProperty(name="Z Distance", subtype="DISTANCE", description="Z Distance From Bottom") -tp.jv_cutout_width = FloatProperty(name="Width", subtype="DISTANCE", description="Width Of Cutout") -tp.jv_cutout_height = FloatProperty(name="Height", subtype="DISTANCE", description="Height Of Cutout") - -tp.jv_pre_jv_dims = StringProperty(default="none") -tp.jv_previous_rotation = StringProperty(default="none") -tp.jv_dims = StringProperty(default="none") -tp.jv_is_slope = BoolProperty(name="Slope Top?", default=False, update=jv_update_object) -tp.jv_over_height = FloatProperty(name="Overall Height", min=0.30486, max=15.2399, default=2.4384, subtype="DISTANCE", - description="Height", update=jv_update_object) -tp.jv_batten_width = FloatProperty(name="Batten Width", min=0.5 / METRIC_INCH, max=4 / METRIC_INCH, - default=2 / METRIC_INCH, subtype="DISTANCE", description="Width Of Batten", - update=jv_update_object) - -tp.jv_is_cutout = BoolProperty(name="Cutouts?", default=False, description="Cut Rectangles Out (Slower)", - update=jv_update_object) -tp.jv_is_screws = BoolProperty(name="Screw Heads?", default=False, description="Add Screw Heads?", - update=jv_update_object) -tp.jv_bevel_width = FloatProperty(name="Bevel Width", min=0.05 / METRIC_INCH, max=0.5 / METRIC_INCH, - default=0.2 / METRIC_INCH, subtype="DISTANCE", update=jv_update_object) -tp.jv_x_offset = FloatProperty(name="X-Offset", min=-2.0 / METRIC_INCH, max=2.0 / METRIC_INCH, default=0.0, - subtype="DISTANCE", update=jv_update_object) -tp.jv_br_width = FloatProperty(name="Brick Width", min=4.0 / METRIC_INCH, max=10.0 / METRIC_INCH, - default=7.625 / METRIC_INCH, subtype="DISTANCE", description="Brick Width", - update=jv_update_object) -tp.jv_br_height = FloatProperty(name="Brick Height", min=2.0 / METRIC_INCH, max=5.0 / METRIC_INCH, - default=2.375 / METRIC_INCH, subtype="DISTANCE", description="Brick Height", - update=jv_update_object) -tp.jv_br_ran_offset = BoolProperty(name="Random Offset?", default=False, description="Random Offset Between Rows", - update=jv_update_object) -tp.jv_br_offset = FloatProperty(name="Brick Offset", subtype="PERCENTAGE", min=0, max=100.0, default=50.0, - description="Brick Offset Between Rows", update=jv_update_object) -tp.jv_br_gap = FloatProperty(name="Gap", min=0.1 / METRIC_INCH, max=1 / METRIC_INCH, default=0.5 / METRIC_INCH, - subtype="DISTANCE", description="Gap Between Bricks", update=jv_update_object) -tp.jv_br_m_depth = FloatProperty(name="Mortar Depth", min=0.1 / METRIC_INCH, max=1.0 / METRIC_INCH, - default=0.25 / METRIC_INCH, subtype="DISTANCE", description="Mortar Depth", - update=jv_update_object) -tp.jv_br_vary = FloatProperty(name="Offset Varience", subtype="PERCENTAGE", min=0, max=100, default=50, - description="Offset Varience", update=jv_update_object) -tp.jv_bump_type = EnumProperty(items=(("1", "Dimpled", ""), ("2", "Ridges", ""), ("3", "Flaky", ""), - ("4", "Smooth", "")), name="Bump Type") -tp.jv_color_style = EnumProperty(items=(("constant", "Constant", "Single Color"), - ("speckled", "Speckled", "Speckled Pattern"), - ("multiple", "Multiple", "Two Mixed Colors"), - ("extreme", "Extreme", "Three Mixed Colors")), name="Color Style") -tp.jv_color2 = FloatVectorProperty(name="Color 2", subtype="COLOR", default=(1.0, 1.0, 1.0), min=0.0, max=1.0, - description="Color 2 For Siding") -tp.jv_color3 = FloatVectorProperty(name="Color 3", subtype="COLOR", default=(1.0, 1.0, 1.0), min=0.0, max=1.0, - description="Color 3 For Siding") -tp.jv_color_sharp = FloatProperty(name="Color Sharpness", min=0.0, max=10.0, default=1.0, - description="Sharpness Of Color Edges") -tp.jv_mortar_color = FloatVectorProperty(name="Mortar Color", subtype="COLOR", default=(1.0, 1.0, 1.0), min=0.0, - max=1.0, description="Color For Mortar") -tp.jv_mortar_bump = FloatProperty(name="Mortar Bump", min=0.0, max=1.0, default=0.25, description="Mortar Bump Amount") -tp.jv_brick_bump = FloatProperty(name="Brick Bump", min=0.0, max=1.0, default=0.25, description="Brick Bump Amount") -tp.jv_color_scale = FloatProperty(name="Color Scale", min=0.01, max=20.0, default=1.0, description="Color Scale") -tp.jv_bump_scale = FloatProperty(name="Bump Scale", min=0.01, max=20.0, default=1.0, description="Bump Scale") -tp.jv_is_corner = BoolProperty(name="Usable Corners?", default=False, description="Alternate Ends To Allow Corners", - update=jv_update_object) -tp.jv_is_invert = BoolProperty(name="Flip Rows?", default=False, description="Flip Offset Staggering", - update=jv_update_object) -tp.jv_is_soldier = BoolProperty(name="Soldier Bricks?", default=False, description="Bricks Above Cutouts", - update=jv_update_object) -tp.jv_is_left = BoolProperty(name="Corners Left?", default=True, description="Usable Corners On Left", - update=jv_update_object) -tp.jv_is_right = BoolProperty(name="Corners Right?", default=True, description="Usable Corners On Right", - update=jv_update_object) -tp.jv_av_width = FloatProperty(name="Average Width", default=10.00 / METRIC_INCH, min=4.00 / METRIC_INCH, - max=36.00 / METRIC_INCH, subtype="DISTANCE", description="Average Width Of Stones", - update=jv_update_object) -tp.jv_av_height = FloatProperty(name="Average Height", default=6.00 / METRIC_INCH, min=2.00 / METRIC_INCH, - max=36.00 / METRIC_INCH, subtype="DISTANCE", description="Average Height Of Stones", - update=jv_update_object) -tp.jv_random_size = FloatProperty(name="Size Randomness", default=25.00, max=100.00, min=0.00, subtype="PERCENTAGE", - description="Size Randomness", update=jv_update_object) -tp.jv_random_bump = FloatProperty(name="Bump Randomness", default=25.00, max=100.00, min=0.00, subtype="PERCENTAGE", - description="Bump Randomness", update=jv_update_object) -tp.jv_st_m_depth = FloatProperty(name="Mortar Depth", default=1.5 / METRIC_INCH, min=0.5 / METRIC_INCH, - max=3.0 / METRIC_INCH, subtype="DISTANCE", description="Depth Of Mortar", - update=jv_update_object) -tp.jv_sb_mat_type = EnumProperty(name="", items=(("1", "Image", ""), ("2", "Procedural", "")), default="1", - description="Stone Material Type") -# stair specific -tp.jv_overhang_style = EnumProperty(items=(("1", "Normal", ""), ("2", "Right", ""), ("3", "Left", ""), - ("4", "Both", "")), default="1", description="Overhang Style", - update=jv_update_object, name="") -tp.jv_num_steps = IntProperty(name="Number Of Steps", min=1, max=24, default=13, update=jv_update_object) -tp.jv_num_steps2 = IntProperty(name="Number Of Steps", min=1, max=48, default=13, update=jv_update_object) -tp.jv_tread_width = FloatProperty(name="Tread Width", min=9.0 / METRIC_INCH, max=16.0 / METRIC_INCH, - default=9.5 / METRIC_INCH, subtype="DISTANCE", update=jv_update_object) -tp.jv_riser_height = FloatProperty(name="Riser Height", min=5.0 / METRIC_INCH, max=8.0 / METRIC_INCH, - default=7.4 / METRIC_INCH, subtype="DISTANCE", update=jv_update_object) -tp.jv_over_front = FloatProperty(name="Front Overhang", min=0.0, max=1.25 / METRIC_INCH, default=1.0 / METRIC_INCH, - subtype="DISTANCE", update=jv_update_object) -tp.jv_over_sides = FloatProperty(name="Side Overhang", min=0.0, max=2.0 / METRIC_INCH, default=1.0 / METRIC_INCH, - subtype="DISTANCE", update=jv_update_object) -tp.jv_stair_width = FloatProperty(name="Stair Width", min=36.0 / METRIC_INCH, max=60.0 / METRIC_INCH, - default=40.0 / METRIC_INCH, subtype="DISTANCE", update=jv_update_object) -tp.jv_is_riser = BoolProperty(name="Risers?", default=True, update=jv_update_object) -tp.jv_is_custom_tread = BoolProperty(name="Custom Treads?", default=False, update=jv_update_object) -tp.jv_custom_treads = StringProperty(name="Custom Tread", default="", update=jv_update_object) -tp.jv_num_landings = IntProperty(name="Number Of Landings", min=0, max=2, default=0, update=jv_update_object) -tp.jv_is_close_sides = BoolProperty(name="Close Sides?", default=False, update=jv_update_object) -tp.jv_set_steps_in = BoolProperty(name="Set Steps In?", default=False, update=jv_update_object) -tp.jv_is_landing = BoolProperty(name="Create Landings?", default=True, update=jv_update_object) -tp.jv_is_light = BoolProperty(name="Allow Recessed Lights?", default=False, update=jv_update_object, - description="Space Middle Step Jacks To Allow Recessed Lights") -tp.jv_num_steps0 = IntProperty(name="Number Of Steps", min=1, max=24, default=13, update=jv_update_object) -tp.jv_tread_width0 = FloatProperty(name="Tread Width", min=9.0 / METRIC_INCH, max=16.0 / METRIC_INCH, - default=9.5 / METRIC_INCH, subtype="DISTANCE", update=jv_update_object) -tp.jv_riser_height0 = FloatProperty(name="Riser Height", min=5.0 / METRIC_INCH, max=8.0 / METRIC_INCH, - default=7.4 / METRIC_INCH, subtype="DISTANCE", update=jv_update_object) -tp.jv_landing_depth0 = FloatProperty(name="Landing 1 Depth", min=36 / METRIC_INCH, max=60 / METRIC_INCH, - default=40 / METRIC_INCH, subtype="DISTANCE", update=jv_update_object) -tp.jv_landing_rot0 = EnumProperty(items=(("1", "Forwards", ""), ("2", "Left", ""), ("3", "Right", "")), - update=jv_update_object, name="") -tp.jv_over_front0 = FloatProperty(name="Front Overhang", min=0.0, max=1.25 / METRIC_INCH, default=1.0 / METRIC_INCH, - subtype="DISTANCE", update=jv_update_object) -tp.jv_over_sides0 = FloatProperty(name="Side Overhang", min=0.0, max=2.0 / METRIC_INCH, default=1.0 / METRIC_INCH, - subtype="DISTANCE", update=jv_update_object) -tp.jv_overhang_style0 = EnumProperty(items=(("1", "Normal", ""), ("2", "Right", ""), ("3", "Left", ""), - ("4", "Both", "")), default="1", description="Overhang Style", - update=jv_update_object, name="") -tp.jv_is_backwards0 = BoolProperty(name="Turn Backwards?", default=False, update=jv_update_object) -tp.jv_num_steps1 = IntProperty(name="Number Of Steps", min=1, max=24, default=13, update=jv_update_object) -tp.jv_landing_rot1 = EnumProperty(items=(("1", "Forwards", ""), ("2", "Left", ""), ("3", "Right", "")), - update=jv_update_object, name="") -tp.jv_tread_width1 = FloatProperty(name="Tread Width", min=9.0 / METRIC_INCH, max=16.0 / METRIC_INCH, - default=9.5 / METRIC_INCH, subtype="DISTANCE", update=jv_update_object) -tp.jv_riser_height1 = FloatProperty(name="Riser Height", min=5.0 / METRIC_INCH, max=8.0 / METRIC_INCH, - default=7.4 / METRIC_INCH, subtype="DISTANCE", update=jv_update_object) -tp.jv_landing_depth1 = FloatProperty(name="Landing 2 Depth", min=36 / METRIC_INCH, max=60 / METRIC_INCH, - default=40 / METRIC_INCH, subtype="DISTANCE", update=jv_update_object) -tp.jv_over_front1 = FloatProperty(name="Front Overhang", min=0.0, max=1.25 / METRIC_INCH, - default=1.0 / METRIC_INCH, subtype="DISTANCE", update=jv_update_object) -tp.jv_over_sides1 = FloatProperty(name="Side Overhang", min=0.0, max=2.0 / METRIC_INCH, - default=1.0 / METRIC_INCH, subtype="DISTANCE", update=jv_update_object) -tp.jv_overhang_style1 = EnumProperty(items=(("1", "Normal", ""), ("2", "Right", ""), ("3", "Left", ""), - ("4", "Both", "")), default="1", description="Overhang Style", - update=jv_update_object, name="") -tp.jv_is_backwards1 = BoolProperty(name="Turn Backwards?", default=False, update=jv_update_object) -tp.jv_winding_rot = EnumProperty(name="", items=(("1", "-90", ""), ("2", "-45", ""), ("3", "45", ""), ("4", "90", "")), - default="3", update=jv_update_object) -tp.jv_step_begin_rot = IntProperty(name="Stair To Begin Rotation On", update=jv_update_object, min=1, max=13, default=6) -tp.jv_spiral_rot = FloatProperty(name="Total Rotation", unit="ROTATION", min=radians(-720), max=radians(720), - default=radians(180), update=jv_update_object) -tp.jv_pole_dia = FloatProperty(name="Pole Diameter", min=3.0 / METRIC_INCH, max=10.0 / METRIC_INCH, - default=4.0 / METRIC_INCH, subtype="DISTANCE", update=jv_update_object) -tp.jv_pole_res = IntProperty(name="Pole Resolution", min=8, max=64, default=16, update=jv_update_object) -tp.jv_tread_res = IntProperty(name="Tread Resolution", min=0, max=8, default=0, update=jv_update_object) -tp.jv_im_scale2 = FloatProperty(name="Image Scale", max=10.0, min=0.1, default=1.0, description="Change Image Scaling") -tp.jv_col_image2 = StringProperty(name="", subtype="FILE_PATH", description="File Path For Color Image") -tp.jv_is_bump2 = BoolProperty(name="Normal Map?", default=False, description="Add Normal To Material?") -tp.jv_norm_image2 = StringProperty(name="", subtype="FILE_PATH", description="Filepath For Normal Map Image") -tp.jv_bump_amo2 = FloatProperty(name="Normal Stength", min=0.001, max=2.000, default=0.250, - description="Normal Map Strength") -tp.jv_is_rotate2 = BoolProperty(name="Rotate Image?", default=False, description="Rotate Image 90 Degrees") +if __name__ == "__main__": + register() diff --git a/jv_roofing.py b/jv_roofing.py index 55f2ed2..7a7d515 100644 --- a/jv_roofing.py +++ b/jv_roofing.py @@ -1,1428 +1,478 @@ -# This file is part of JARCH Vis -# -# JARCH Vis is free software: you can redistribute it and/or modify +# This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # -# JARCH Vis is distributed in the hope that it will be useful, +# This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with JARCH Vis. If not, see . - -import bpy -from mathutils import Vector, Euler, Matrix -from math import atan, degrees, cos, tan, radians, sqrt -from . jv_utils import point_rotation, round_tuple, METRIC_INCH, HI, I, rot_from_normal, \ - unwrap_object, random_uvs -from . jv_materials import glossy_diffuse_material, image_material -from random import uniform -import bmesh -# import jv_properties -from bpy.props import * - -con = METRIC_INCH - - -def tin_normal(wh, ow, slope): - cur_x = 0.0 - cur_y = -(wh / 2) - faces, verts = [], [] - - # calculate overall depth/oh - oh = (wh / 2) / cos(atan((((wh / 2) * slope) / 12) / (wh / 2))) - - # set up small variables - eti, nti = 0.8 / con, 0.9 / con - fei = (5 / 8) / con - nfei = 0.6875 / con - otqi = 1.75 / con - ofei = I+fei - ohi = I+HI - qi, ei, osi = HI / 2, HI / 4, HI / 8 - - while cur_x < ow: - p = len(verts) - v2 = [] - a_e = False # verts holder 2, at_edge for putting in last set of verts - - for i in range(4): - z = 0.0 if i else osi - - v2 += [(cur_x, cur_y, z), (cur_x, cur_y+oh, z), (cur_x+HI, cur_y, eti+z), (cur_x+HI, cur_y+oh, eti+z), - (cur_x+fei, cur_y, nti+z), (cur_x+fei, cur_y+oh, nti+z), (cur_x+nfei, cur_y, I+z), - (cur_x+nfei, cur_y+oh, I+z)] - cur_x += otqi - v2 += [(cur_x-nfei, cur_y, I+z), (cur_x-nfei, cur_y+oh, I+z), (cur_x-fei, cur_y, nti+z), - (cur_x-fei, cur_y+oh, nti+z)] # Right Mid Rib - v2 += [(cur_x-HI, cur_y, eti+z), (cur_x-HI, cur_y+oh, eti+z), (cur_x, cur_y, z), (cur_x, cur_y+oh, z)] - cur_x += ofei - - for i2 in range(2): - v2 += [(cur_x, cur_y, 0.0), (cur_x, cur_y+oh, 0.0), (cur_x+qi, cur_y, ei), (cur_x+qi, cur_y+oh, ei)] - cur_x += I - v2 += [(cur_x, cur_y, ei), (cur_x, cur_y+oh, ei), (cur_x+qi, cur_y, 0.0), (cur_x+qi, cur_y+oh, 0.0)] - cur_x += ohi+qi - - cur_x += ei - - v2 += [(cur_x, cur_y, 0.0), (cur_x, cur_y+oh, 0.0), (cur_x+HI, cur_y, eti), (cur_x+HI, cur_y+oh, eti), - (cur_x+fei, cur_y, nti), (cur_x+fei, cur_y+oh, nti)] # Left Top Of Rib - v2 += [(cur_x+nfei, cur_y, I), (cur_x+nfei, cur_y+oh, I)] - cur_x += otqi - v2 += [(cur_x-nfei, cur_y, I), (cur_x-nfei, cur_y+oh, I), (cur_x-fei, cur_y, nti), (cur_x-fei, cur_y+oh, nti)] - v2 += [(cur_x-HI, cur_y, eti), (cur_x-HI, cur_y+oh, eti), (cur_x, cur_y, 0.0), (cur_x, cur_y+oh, 0.0)] - cur_x -= otqi - - vts = [] - if cur_x+otqi > ow: # chop off extra - for i in range(len(v2)): - if v2[i][0] <= ow: - vts.append(v2[i]) - elif v2[i][0] > ow and not a_e: - a_e = True - b_o = v2[i-1] - f_o = v2[i] - dif_x = f_o[0]-b_o[0] - dif_z = f_o[2]-b_o[2] - r_r = dif_z / dif_x - b = b_o[2]-(r_r * b_o[0]) - z2 = (ow * r_r)+b - vts += [(ow, cur_y, z2), (ow, cur_y, z2)] - - f_t = int((len(vts) / 2)-1) - else: - vts = v2 - f_t = 71 - - for i in vts: - verts.append(i) - for i in range(f_t): - faces.append((p, p+2, p+3, p+1)) - p += 2 - - # apply rotation - rot = atan(slope / 12) - - for num in range(1, len(verts), 2): - point = point_rotation((verts[num][1], verts[num][2]), (cur_y, verts[num][2]), rot) - verts[num] = [verts[num][0], point[0], point[1]] - - return verts, faces - - -def tin_angular(wh, ow, slope): - cur_x, cur_y, cur_z = 0.0, -(wh / 2), 0.0 - verts, faces = [], [] - - # variables - osi = (1 / 16) / con - oqi = (5 / 4) / con - ohi = (3 / 2) / con - ti = ohi+HI - qi = (1 / 4) / con - ei = (1 / 8) / con - - # calculate overall depth/oh - oh = (wh / 2) / cos(atan((((wh / 2) * slope) / 12) / (wh / 2))) - - # main loop - while cur_x < ow: - p = len(verts) - - v2 = [] - a_e = False - for i in range(3): - z = 0.0 if i else -osi - - v2 += [(cur_x, cur_y, z), (cur_x, cur_y+oh, z), (cur_x+HI, cur_y, oqi+z), (cur_x+HI, cur_y+oh, oqi+z), - (cur_x+ohi, cur_y, oqi+z), (cur_x+ohi, cur_y+oh, oqi+z), (cur_x+ti, cur_y, z), - (cur_x+ti, cur_y+oh, z)] - cur_x += 2 * ti - - for i2 in range(2): - v2 += [(cur_x, cur_y, 0.0), (cur_x, cur_y+oh, z), (cur_x+qi, cur_y, ei), (cur_x+qi, cur_y+oh, ei), - (cur_x+ohi, cur_y, ei), (cur_x+ohi, cur_y+oh, ei)] - cur_x += ohi - v2 += [(cur_x+qi, cur_y, 0.0), (cur_x+qi, cur_y+oh, 0.0)] - cur_x += qi+ti+HI - - cur_x -= HI - - v2 += [(cur_x, cur_y, 0.0), (cur_x, cur_y+oh, 0.0), (cur_x+HI, cur_y, oqi), (cur_x+HI, cur_y+oh, oqi), - (cur_x+ohi, cur_y, oqi), (cur_x+ohi, cur_y+oh, oqi), (cur_x+ti, cur_y, 0.0), (cur_x+ti, cur_y+oh, 0.0)] - - vts = [] - if cur_x+ti > ow: # cut off extra - for i in range(len(v2)): - if v2[i][0] <= ow: - vts.append(v2[i]) - elif v2[i][0] > ow and not a_e: - a_e = True - b_o = v2[i-1] - f_o = v2[i] - dif_x = f_o[0]-b_o[0] - dif_z = f_o[2]-b_o[2] - r_r = dif_z / dif_x - b = b_o[2]-(r_r * b_o[0]) - z2 = (ow * r_r)+b - - vts += [(ow, cur_y, z2), (ow, cur_y+oh, z2)] - - f_t = int((len(vts) / 2)-1) - else: - vts = v2 - f_t = 38 - - for i in vts: - verts.append(i) - # faces - for i in range(f_t): - faces.append((p, p+2, p+3, p+1)) - p += 2 - - # adjust points for slope - rot = atan(slope / 12) - - for num in range(1, len(verts), 2): - point = point_rotation((verts[num][1], verts[num][2]), (cur_y, verts[num][2]), rot) - verts[num] = [verts[num][0], point[0], point[1]] - - return verts, faces - - -def tin_standing_seam(wh, ow, slope): - verts, faces = [], [] - cur_x, cur_y, cur_z = 0.0, -(wh / 2), 0.0 - - # variables - qi = 0.25 / con - fei = 0.625 / con - otei = 1.375 / con - osi = 0.0625 / con - ei = 0.125 / con - si = 16 / con - - # calculate overall height - oh = (wh / 2) / cos(atan((((wh / 2) * slope) / 12) / (wh / 2))) - - while cur_x < ow: - p = len(verts) - v2 = [] - a_e = False - # left side - v2 += [(cur_x+qi, cur_y, cur_z+otei), (cur_x+qi, cur_y+oh, cur_z+otei), (cur_x+qi, cur_y, cur_z+qi), - (cur_x+qi, cur_y+oh, cur_z+qi), (cur_x+ei, cur_y, cur_z+qi), (cur_x+ei, cur_y+oh, cur_z+qi), - (cur_x+ei, cur_y, cur_z+HI), (cur_x+ei, cur_y+oh, cur_z+HI), (cur_x, cur_y, cur_z+HI), - (cur_x, cur_y+oh, cur_z+HI), (cur_x, cur_y, cur_z), (cur_x, cur_y+oh, cur_z)] - # right side - cur_x += si - v2 += [(cur_x, cur_y, cur_z), (cur_x, cur_y+oh, cur_z), (cur_x+qi, cur_y, cur_z+otei), - (cur_x+qi, cur_y+oh, cur_z+otei), (cur_x+qi+ei, cur_y, cur_z+otei), (cur_x+qi+ei, cur_y+oh, cur_z+otei), - (cur_x+fei, cur_y, cur_z+osi), (cur_x+fei, cur_y+oh, cur_z+osi)] - cur_x += ei - - # cut-off extra - if cur_x+HI > ow: - for i in range(len(v2)): - if v2[i][0] <= ow: - vts.append(v2[i]) - elif v2[i][0] > ow and not a_e: - a_e = True - b_o = v2[i-1] - f_o = v2[i] - dif_x = f_o[0]-b_o[0] - dif_z = f_o[2]-b_o[2] - r_r = dif_z / dif_x - b = b_o[2]-(r_r * b_o[0]) - z2 = (ow * r_r)+b - - vts += [(ow, cur_y, z2), (ow, cur_y+oh, z2)] - f_t = int((len(vts) / 2)-1) - else: - vts = v2 - f_t = 9 - - for i in vts: - verts.append(i) - # faces - for i in range(f_t): - faces.append((p, p+2, p+3, p+1)) - p += 2 - - # adjust points for slope - rot = atan(slope / 12) - - for num in range(1, len(verts), 2): - point = point_rotation((verts[num][1], verts[num][2]), (cur_y, verts[num][2]), rot) - verts[num] = (verts[num][0], point[0], point[1]) - - return verts, faces - - -def shingles_3tab(wh, ow, slope): - verts, faces = [], [] - cur_y, cur_z = -(wh / 2), 0.0 - - # constants - of = 12 / con - fi = 5 / con - tsi = 0.125 / con - qi = 0.25 / con - etqi = 11.75 / con - - # calculate overall height - oh = (wh / 2) / cos(atan((((wh / 2) * slope) / 12) / (wh / 2))) - end_y = cur_y+oh - - offset = False - row = 1 - - # row to go under tabs at bottom - verts += [(0, cur_y, -tsi), (0, cur_y, 0), (0, cur_y + fi, -tsi), (0, cur_y + fi, 0), (ow, cur_y, -tsi), - (ow, cur_y, 0), (ow, cur_y + fi, -tsi), (ow, cur_y + fi, 0)] - faces += [(0, 1, 3, 2), (0, 4, 5, 1), (1, 5, 7, 3), (2, 3, 7, 6), (4, 6, 7, 5)] - - # main loop for growing on width - while cur_y < end_y: - last_row_stay = True - cur_x = 0.0 - bj = of # big jump - sj = fi # small jump - p = len(verts) - - # determine if shingle needs to be cut down on y axis - if cur_y+fi < end_y < cur_y+of: - bj = end_y-cur_y - # if last row doesn't stay figure out how much to cut down second row - elif cur_y+of > end_y and cur_y+fi > end_y: - sj = end_y-cur_y - last_row_stay = False - - # determine shingle height at front, middle, and back based on which row it is - if row == 1: - h1, h2, h3 = 0, 0, 0 # height 1, 2, and 3 - elif row == 2: - h1, h2, h3 = tsi, tsi, 0 - else: - h1, h2, h3 = 2 * tsi, tsi, 0 - - # first vertices - verts += [[cur_x, cur_y, h1], [cur_x, cur_y, h1 + tsi], [cur_x, cur_y + sj, h2], - [cur_x, cur_y + sj, h2 + tsi]] - if last_row_stay: - verts += [[cur_x, cur_y + bj, h3], [cur_x, cur_y + bj, h3 + tsi]] - - # loop for growing length - width = qi - sections_placed = 0 - cur_x += etqi / 2 if offset else etqi - - while cur_x < ow: - last_p = len(verts) - - verts += [[cur_x, cur_y, h1], [cur_x, cur_y, h1 + tsi], [cur_x, cur_y + sj, h2], - [cur_x, cur_y + sj, h2 + tsi]] - if last_row_stay: - verts += [[cur_x, cur_y + bj, h3], [cur_x, cur_y + bj, h3 + tsi]] - - cur_x += width - width = etqi if width == qi else qi - sections_placed += 1 - - for i in range(last_p, len(verts), 1): - if verts[i][0] > ow: - verts[i][0] = ow - - verts += [[ow, cur_y, h1], [ow, cur_y, h1 + tsi], [ow, cur_y + sj, h2], [ow, cur_y + sj, h2 + tsi]] - if last_row_stay: - verts += [[ow, cur_y + bj, h3], [ow, cur_y + bj, h3 + tsi]] - sections_placed += 1 - - if last_row_stay: - faces += [(p, p + 1, p + 3, p + 2), (p + 2, p + 3, p + 5, p + 4)] # close in end - - for i in range(int(sections_placed / 2)): - faces += [(p, p + 6, p + 7, p + 1), (p + 1, p + 7, p + 9, p + 3), (p + 3, p + 9, p + 11, p + 5), - (p + 4, p + 5, p + 11, p + 10), (p, p + 2, p + 8, p + 6), (p + 2, p + 4, p + 10, p + 8)] - p += 6 - - if i < sections_placed / 2 - 1 or sections_placed % 2 == 0: - faces += [(p, p + 2, p + 3, p + 1), (p + 2, p + 8, p + 9, p + 3), (p + 6, p + 7, p + 9, p + 8), - (p + 3, p + 9, p + 11, p + 5), (p + 4, p + 5, p + 11, p + 10), - (p + 2, p + 4, p + 10, p + 8)] - p += 6 - - if sections_placed % 2 != 0: - faces += [(p, p + 6, p + 7, p + 1), (p + 1, p + 7, p + 9, p + 3), (p + 3, p + 9, p + 11, p + 5), - (p + 4, p + 5, p + 11, p + 10), (p, p + 2, p + 8, p + 6), (p + 2, p + 4, p + 10, p + 8)] - p += 6 - faces += [(p, p + 2, p + 3, p + 1), (p + 2, p + 4, p + 5, p + 3)] # close end +# along with this program. If not, see . + +from . jv_builder_base import JVBuilderBase +from mathutils import Euler, Vector +from math import atan, cos, radians, sin, asin +from . jv_utils import Units + + +class JVRoofing(JVBuilderBase): + is_cutable = True + is_convertible = True + + @staticmethod + def draw(props, layout): + layout.prop(props, "roofing_pattern", icon="MOD_TRIANGULATE") + + layout.separator() + row = layout.row() + row.prop(props, "width") + row.prop(props, "length") + + if props.convert_source_object is None: + layout.prop(props, "pitch") + + if props.roofing_pattern == "tin_standing_seam": + layout.separator() + layout.prop(props, "pan_width") + elif props.roofing_pattern == "shakes": + layout.separator() + row = layout.row() + row.prop(props, "shake_length") + row.prop(props, "shake_width") + elif props.roofing_pattern == "terracotta": + layout.separator() + row = layout.row() + row.prop(props, "tile_length") + row.prop(props, "terracotta_radius") + + # row offset + if props.roofing_pattern in ("shingles_3_tab", "shakes"): + layout.separator() + row = layout.row() + + row.prop(props, "vary_row_offset", icon="RNDCURVE") + if props.vary_row_offset: + row.prop(props, "row_offset_variance") else: - faces += [(p + 2, p + 4, p + 5, p + 3)] # close end + row.prop(props, "row_offset") + + # thickness + if props.roofing_pattern in ("shingles_3_tab", "shingles_architectural", "shakes", "terracotta"): + layout.separator() + layout.prop(props, "thickness_thin") + + # terracotta spacing and resolution + if props.roofing_pattern == "terracotta": + layout.separator() + layout.prop(props, "terracotta_gap") + + layout.separator() + layout.prop(props, "terracotta_resolution") + + # gap + if props.roofing_pattern == "shakes": + layout.separator() + layout.prop(props, "gap_uniform") + + # mirror + if props.convert_source_object is None: + layout.separator() + layout.prop(props, "mirror", icon="MOD_MIRROR") + + @staticmethod + def update(props, context): + if props.convert_source_object is not None: + mesh = JVRoofing._generate_mesh_from_converted_object(props, context) else: - for i in range(int(sections_placed / 2)): - faces += [(p, p + 1, p + 3, p + 2), (p, p + 4, p + 5, p + 1), (p + 1, p + 5, p + 7, p + 3), - (p + 2, p + 3, p + 7, p + 6), (p + 4, p + 6, p + 7, p + 5)] - p += 8 - - if sections_placed % 2 != 0: - faces += [(p, p + 1, p + 3, p + 2), (p, p + 4, p + 5, p + 1), (p + 1, p + 5, p + 7, p + 3), - (p + 2, p + 3, p + 7, p + 6), (p + 4, p + 6, p + 7, p + 5)] - - cur_y += fi - row += 1 - offset = not offset - - # adjust vertex position to adjust for slope - rot = atan(slope / 12) - - for num in range(len(verts)): - point = point_rotation((verts[num][1], verts[num][2]), (-(wh / 2), verts[num][2]), rot) - verts[num] = (verts[num][0], point[0], point[1]) - - return verts, faces - - -def shingles_arch(wh, ow, slope): - def raised_faces(f, j): - f += [(j, j + 9, j + 10, j + 1), (j + 1, j + 10, j + 11, j + 2), (j + 1, j + 2, j + 5, j + 4), - (j + 2, j + 11, j + 14, j + 5), (j + 5, j + 14, j + 17, j + 8), (j + 7, j + 8, j + 17, j + 16), - (j + 6, j + 7, j + 16, j + 15), (j + 3, j + 6, j + 15, j + 12), (j, j + 3, j + 12, j + 9), - (j + 10, j + 13, j + 14, j + 11)] - - def lower_faces(f, j, subtract): - a = 1 if subtract else 0 - j -= 1 if subtract else 0 - - f += [(j + a, j + 9, j + 10, j + 1 + a), (j + 1 + a, j + 10, j + 13, j + 4), (j + 4, j + 13, j + 14, j + 5), - (j + 5, j + 14, j + 17, j + 8), (j + 7, j + 8, j + 17, j + 16), (j + 6, j + 7, j + 16, j + 15), - (j + a, j + 3, j + 12, j + 9), (j + 3, j + 6, j + 15, j + 12)] - - def lower_faces_vert_removed(f, j): - f += [(j, j + 9, j + 10, j + 1), (j + 1, j + 10, j + 12, j + 4), (j + 4, j + 12, j + 13, j + 5), - (j + 5, j + 13, j + 16, j + 8), (j + 15, j + 7, j + 8, j + 16), (j + 14, j + 6, j + 7, j + 15), - (j + 6, j + 14, j + 11, j + 3), (j + 3, j + 11, j + 9, j), (j + 9, j + 11, j + 12, j + 10), - (j + 11, j + 14, j + 15, j + 12), (j + 12, j + 15, j + 16, j + 13)] - - verts, faces = [], [] - rot = atan(slope / 12) - - cur_y = -(wh / 2) - - # variables - sfei = 6.625 / con - lth = 0.1875 / con - sw = 39.375 / con - third_single = sw / 3 - ffei = 5.625 / con - tth = lth / 2 - two = 2 / con - - oh = (wh / 2) / cos(atan((((wh / 2) * slope) / 12) / (wh / 2))) - end_y = cur_y+oh - - last_row_stay = True - row = 1 - - while cur_y < end_y: - cur_x = 0.0 - sj = ffei - bj = sfei+ffei - v = [] - p = len(verts) # size before anything is added - tabs_placed = 0 - - if cur_y+ffei >= end_y: - last_row_stay = False - sj = end_y-cur_y # small jump replaces ffei and is adjusted for if shingle is not full width - - if cur_y+ffei < end_y <= cur_y+ffei+sfei: - bj = end_y-cur_y # big jump replaces sfei if shingle is not full width, but row can still be placed - - if row == 1: - h1, h2, h3 = 0, 0, 0 # height 1, 2, and 3 - elif row == 2: - h1, h2, h3 = lth, lth, 0 - elif row >= 3: - h1, h2, h3 = 2 * lth, lth, 0 - - v += [(cur_x, cur_y, h1), (cur_x, cur_y, h1 + tth), (cur_x, cur_y + sj, h2), - (cur_x, cur_y + sj, h2 + tth)] - if last_row_stay: - v += [(cur_x, cur_y + sj, h2 + lth), (cur_x, cur_y + bj, h3), (cur_x, cur_y + bj, h3 + tth), - (cur_x, cur_y + bj, h3 + lth)] - - clipped = False - placed_right = True - while cur_x < ow: - cur_x += two - tabs_placed += 1 - width = uniform(3 / con, 7 / con) # width of tab - start_p = len(v) - - # left side of tab - v += [[cur_x, cur_y, h1], [cur_x, cur_y, h1+tth], [cur_x, cur_y, h1+lth], - [cur_x + HI, cur_y + sj, h2], [cur_x + HI, cur_y + sj, h2 + tth], [cur_x + HI, cur_y+sj, h2+lth]] - if last_row_stay: - v += [[cur_x + HI, cur_y + bj, h3], [cur_x + HI, cur_y + bj, h3 + tth], [cur_x + HI, cur_y+bj, h3+lth]] - - if cur_x + HI < ow: - start_p = len(v) # verts so far don't need to be clipped, so update start position for clipping - # right side of tab, only do if previous set didn't go to far - v += [[cur_x + width + I, cur_y, h1], [cur_x + width + I, cur_y, h1+tth], - [cur_x + width + I, cur_y, h1+lth], [cur_x + width + HI, cur_y + sj, h2], - [cur_x + width + HI, cur_y + sj, h2 + tth], [cur_x + width + HI, cur_y + sj, h2 + lth]] - - if last_row_stay: - v += [[cur_x + width + HI, cur_y + bj, h3], [cur_x + width + HI, cur_y + bj, h3 + tth], - [cur_x + width + HI, cur_y + bj, h3 + lth]] - else: - placed_right = False - - # shorten if needed - if cur_x + width + I > ow or not placed_right: - clipped = True - for i in range(start_p, len(v), 1): - v[i][0] = ow - - if clipped and not placed_right and last_row_stay: # remove vertex at corner because no face will use it - del v[len(v) - 7] - elif clipped and not placed_right and not last_row_stay: # remove vertex when no last row - del v[len(v) - 4] - del v[len(v) - 1] - - cur_x += uniform(width, third_single) # space between tabs - - if not clipped: # manually put in end vertices - v += [(ow, cur_y, h1), (ow, cur_y, h1 + tth), (ow, cur_y + sj, h2), (ow, cur_y + sj, h2 + tth)] - if last_row_stay: - v += [(ow, cur_y + sj, h2 + lth), (ow, cur_y + bj, h3), (ow, cur_y + bj, h3 + tth), - (ow, cur_y + bj, h3 + lth)] - - for i in v: - verts.append(i) - - if last_row_stay: - lower_faces(faces, p, True) - faces += [(p, p + 1, p + 3, p + 2), (p + 2, p + 3, p + 6, p + 5), (p + 3, p + 4, p + 7, p + 6)] # close end - p += 8 - for tab in range(tabs_placed - 1): - raised_faces(faces, p) - p += 9 - if clipped and not placed_right and tab == tabs_placed - 2: # bottom, right, top vertex removed - lower_faces_vert_removed(faces, p) - else: - lower_faces(faces, p, False) - p += 9 - - if placed_right: # go one step further if placed right - raised_faces(faces, p) - p += 9 + mesh = JVRoofing._start(context) + verts, faces = JVRoofing._geometry(props, (props.length, props.width / cos(atan(props.pitch / 12)))) + JVRoofing._build_mesh_from_geometry(mesh, verts, faces) + + # overall dimension cutting - length + if props.roofing_pattern in ("tin_regular", "tin_angular", "tin_standing_seam", "shingles_3_tab", + "shingles_architectural", "terracotta"): + JVRoofing._cut_meshes([mesh], [ + ((props.length, 0, 0), (-1, 0, 0)) + ]) + # overall dimension cutting - width + if props.roofing_pattern in ("shingles_3_tab", "shingles_architectural", "terracotta"): + JVRoofing._cut_meshes([mesh], [ + ((0, props.width / cos(atan(props.pitch / 12)), 0), (0, -1, 0)) + ]) + + # rotate + rot = atan(props.pitch / 12) + rotation = Euler((rot, 0, 0)) + JVRoofing._rotate_mesh_vertices(mesh, rotation) + + # mirror + if props.mirror: + shift = Vector((0, -props.width, 0)) + for v in mesh.verts: + v.co += shift + + JVRoofing._mirror(mesh) + + # cutouts + if props.add_cutouts: + JVRoofing._cutouts(mesh, props, context.object.matrix_world) + + original_edges = mesh.edges[:] + + # solidify + new_geometry = [] + if props.roofing_pattern in ("shingles_3_tab", "shingles_architectural", "shakes", "terracotta"): + new_geometry += JVRoofing._solidify(mesh, props.thickness_thin) + + # main material index + JVRoofing._add_material_index(mesh.faces, 0) + + # add uv seams + JVRoofing._add_uv_seams_for_solidified_plane(new_geometry, original_edges, mesh) + + JVRoofing._finish(context, mesh) + JVRoofing._uv_unwrap() + + @staticmethod + def _geometry(props, dims: tuple): + verts, faces = [], [] + + getattr(JVRoofing, "_{}".format(props.roofing_pattern))(dims, props, verts, faces) + + return verts, faces + + @staticmethod + def _tin_regular(dims: tuple, props, verts, faces): + ridge_steps = ( + (0, 0), + (Units.H_INCH, Units.TQ_INCH), + (5 * Units.ETH_INCH, 7 * Units.ETH_INCH), + (11 * Units.STH_INCH, Units.INCH), + (17 * Units.STH_INCH, Units.INCH), + (9 * Units.ETH_INCH, 7 * Units.ETH_INCH), + (5 * Units.Q_INCH, 3 * Units.Q_INCH) + ) + + valley_steps = ( + (0, 0), + (13 * Units.ETH_INCH, 0), + (15 * Units.ETH_INCH, Units.ETH_INCH), + (21 * Units.ETH_INCH, Units.ETH_INCH) + ) + + # diagonal distance to prep for rotation of vertices + upper_x, upper_y = dims + offset_between_valley_accents = 23 * Units.ETH_INCH + for y in (0, upper_y): + x = 0 + while x < upper_x + offset_between_valley_accents: + for step in ridge_steps: + verts.append((x + step[0], y, step[1])) + x += 7 * Units.Q_INCH + + for _ in range(2): + for step in valley_steps: + verts.append((x + step[0], y, step[1])) + x += offset_between_valley_accents + + verts.append((x, y, 0)) # finish valley ridge + x += offset_between_valley_accents - Units.INCH - if clipped and placed_right: # close end in - faces += [(p, p + 3, p + 4, p + 1), (p + 3, p + 6, p + 7, p + 4), (p + 4, p + 7, p + 8, p + 5)] - elif clipped and not placed_right: # bottom, right, top vertex was removed - faces += [(p, p + 2, p + 3, p + 1), (p + 2, p + 5, p + 6, p + 3), (p + 3, p + 6, p + 7, p + 4)] + # faces + offset = len(verts) // 2 + for i in range(offset - 1): + faces.append((i, i + 1, i + offset + 1, i + offset)) + + @staticmethod + def _tin_angular(dims: tuple, props, verts, faces): + pan = 3*Units.INCH + ridge_steps = ((0, 0), (Units.H_INCH, 5*Units.Q_INCH), (3*Units.H_INCH, 5*Units.Q_INCH), (2*Units.INCH, 0)) + valley_steps = ((0, 0), (pan, 0), (pan + Units.Q_INCH, Units.ETH_INCH), (pan + 3*Units.H_INCH, Units.ETH_INCH)) + + upper_x, upper_y = dims + for y in (0, upper_y): + x = 0 + while x < upper_x+pan: + for step in ridge_steps: + verts.append((x+step[0], y, step[1])) + x += 2 * Units.INCH + + for _ in range(2): + for step in valley_steps: + verts.append((x+step[0], y, step[1])) + x += pan + 7*Units.Q_INCH + + verts.append((x, y, 0)) + x += pan - if not clipped: # build faces for end that was manually put in - lower_faces_vert_removed(faces, p) - else: - faces += [(p, p + 1, p + 3, p + 2), (p, p + 4, p + 5, p + 1), (p + 1, p + 5, p + 8, p + 3), - (p, p + 2, p + 7, p + 4), (p + 2, p + 3, p + 8, p + 7)] # first lower and close end - p += 4 - for tab in range(tabs_placed - 1): - # raised - faces += [(p, p + 6, p + 7, p + 1), (p + 1, p + 7, p + 8, p + 2), (p + 2, p + 8, p + 11, p + 5), - (p + 1, p + 2, p + 5, p + 4), (p + 7, p + 10, p + 11, p + 8), - (p + 3, p + 4, p + 10, p + 9), (p + 4, p + 5, p + 11, p + 10), (p, p + 3, p + 9, p + 6)] - p += 6 - if clipped and not placed_right and tab == tabs_placed - 2: # vertex removed - faces += [(p, p + 6, p + 7, p + 1), (p + 1, p + 7, p + 9, p + 4), (p + 3, p + 4, p + 9, p + 8), - (p, p + 3, p + 8, p + 6)] - else: - faces += [(p, p + 6, p + 7, p + 1), (p + 1, p + 7, p + 10, p + 4), (p + 3, p + 4, p + 10, p + 9), - (p, p + 3, p + 9, p + 6)] # lower - p += 6 - - if placed_right: - faces += [(p, p + 6, p + 7, p + 1), (p + 1, p + 7, p + 8, p + 2), (p + 2, p + 8, p + 11, p + 5), - (p + 1, p + 2, p + 5, p + 4), (p + 7, p + 10, p + 11, p + 8), - (p + 3, p + 4, p + 10, p + 9), (p + 4, p + 5, p + 11, p + 10), (p, p + 3, p + 9, p + 6)] - p += 6 - - if clipped and placed_right: # close end in - faces += [(p, p + 3, p + 4, p + 1)] - elif clipped and not placed_right: # close end in - faces += [(p, p + 2, p + 3, p + 1)] - - if not clipped: - faces += [(p, p + 6, p + 7, p + 1), (p + 1, p + 7, p + 9, p + 4), (p + 3, p + 4, p + 9, p + 8), - (p, p + 3, p + 8, p + 6), (p + 6, p + 8, p + 9, p + 7)] - - row += 1 - cur_y += ffei - - for num in range(len(verts)): - point = point_rotation((verts[num][1], verts[num][2]), (-(wh / 2), verts[num][2]), rot) - verts[num] = (verts[num][0], point[0], point[1]) - - return verts, faces - - -def terra_cotta(wh, ow, slope, res, rad): - verts, faces = [], [] - rot = atan(slope / 12) - - cur_y = -(wh / 2) - - # variables - th = 0.5 / con - st_h = 2 / con - tile_l1 = 18 / con - tail_h = 0.5 / con - rot_dif = radians(180 / (res+1)) - - oh = (wh / 2) / cos(atan((((wh / 2) * slope) / 12) / (wh / 2))) - end_y = cur_y+oh - - # main loop for width - while cur_y < end_y: - cur_x = rad # reset the distances moved length wise each time - - # check to see if tile is to long - tile_l = end_y-cur_y if cur_y+tile_l1 > end_y else tile_l1 - - while cur_x < ow: + # faces + offset = len(verts) // 2 + for i in range(offset - 1): + faces.append((i, i + 1, i + offset + 1, i + offset)) + + @staticmethod + def _tin_standing_seam(dims: tuple, props, verts, faces): + width = props.pan_width + qi, hi, sqi, fei, tei = Units.Q_INCH, Units.H_INCH, 7*Units.Q_INCH, 5*Units.ETH_INCH, 13*Units.ETH_INCH + sei, nsi = 7*Units.ETH_INCH, 9*Units.STH_INCH + + v_offset = 11 + upper_x, upper_y = dims + x = 0 + while x < upper_x: p = len(verts) - cur_z = st_h - next_x, next_z, cut_point = 0, 0, 0 - - # left circle - for i in range(res+2, 0, -1): - cur_rot = rot_dif * i # calculate current rotation - # first set for position - x, z = point_rotation((cur_x-rad, cur_z), (cur_x, cur_z), cur_rot+radians(180)) - - if x <= ow: - xz = point_rotation([(cur_x-rad+th, cur_z), (cur_x-rad-th, cur_z), (cur_x-rad, cur_z)], - (cur_x, cur_z), cur_rot+radians(180)) - - verts += [(x, cur_y, z + tail_h), (xz[0][0], cur_y + tile_l, xz[0][1]), - (xz[1][0], cur_y, xz[1][1] + tail_h), (xz[2][0], cur_y + tile_l, xz[2][1])] - - next_x = x - next_z = z - cut_point += 1 - - cur_x = next_x+rad - cur_z = next_z - - # right circle - for i in range(0, res+1, 1): - cur_rot = rot_dif * (i+1) - # first set for position - x, z = point_rotation((cur_x-rad, cur_z), (cur_x, cur_z), cur_rot) - - if x <= ow: - xz = point_rotation([(cur_x-rad-th, cur_z), (cur_x-rad+th, cur_z), (cur_x-rad, cur_z)], - (cur_x, cur_z), cur_rot) - - verts += [(x, cur_y, z+tail_h), (xz[0][0], cur_y+tile_l, xz[0][1]), - (xz[1][0], cur_y, xz[1][1]+tail_h), (xz[2][0], cur_y+tile_l, xz[2][1])] - cut_point += 1 - - faces += [(p, p+2, p+3, p+1)] - for i in range(cut_point-1): - faces += [(p+2, p+6, p+7, p+3), (p, p+1, p+5, p+4), (p, p+4, p+6, p+2), (p+1, p+3, p+7, p+5)] - p += 4 - - faces += [(p, p+1, p+3, p+2)] - - cur_x += rad * 1.25 - - cur_y += tile_l1 * 0.75 - - for num in range(len(verts)): - point = point_rotation((verts[num][1], verts[num][2]), (-(wh / 2), verts[num][2]), rot) - verts[num] = (verts[num][0], point[0], point[1]) - - return verts, faces - - -def create_roofing(context, mode, mat, shingles, tin, length, width, slope, res, radius): - verts, faces = [], [] - return_ob = "" - - # tin - if mat == "1": - if tin == "1": - verts, faces = tin_normal(width, length, slope) - elif tin == "2": - verts, faces = tin_angular(width, length, slope) - else: - verts, faces = tin_standing_seam(width, length, slope) - - # shingles - elif mat == "2": - if shingles == "1": - verts, faces = shingles_arch(width, length, slope) - elif shingles == "2": - verts, faces = shingles_3tab(width, length, slope) - - # terra cotta - else: - verts, faces = terra_cotta(width, length, slope, res, radius) - - # decide whether to replace current objects mesh or create a new object - if mode == "add": - mesh = bpy.data.meshes.new("roofing") - mesh.from_pydata(verts, [], faces) - context.object.data = mesh - - elif mode == "convert": - mesh = bpy.data.meshes.new("roofing") - mesh.from_pydata(verts, [], faces) - return_ob = bpy.data.objects.new("roofing", mesh) - context.scene.objects.link(return_ob) - - return return_ob - - -def object_dimensions(obj, z_rot): - matrix = Matrix.Rotation(z_rot, 4, "Z") - min_x, min_y, min_z, max_x, max_y, max_z = obj.data.vertices[0].co.to_tuple() * 2 - - for i in obj.data.vertices: - co = i.co - if co[0] < min_x: - min_x = co[0] - if co[0] > max_x: - max_x = co[0] - - if co[1] < min_y: - min_y = co[1] - if co[1] > max_y: - max_y = co[1] - - if co[2] < min_z: - min_z = co[2] - if co[2] > max_z: - max_z = co[2] - - dims = matrix * Vector((max_x - min_x, max_y - min_y, max_z - min_z)) - width = sqrt(dims[0] ** 2 + dims[2] ** 2) - height = dims[1] - return width, height - - -def update_roofing(self, context): - m_ob = context.object - dup_ob = None - obs = [] - ft = True - - # get materials on object - materials = [] - for i in m_ob.data.materials: - materials.append(i.name) - - # deselect all objects - sel = [] - for i in context.selected_objects: - sel.append(i.name) - i.select = False - - # create backup object for cutting to use subsequent times - if m_ob.jv_main_name == "none" and m_ob.jv_object_add == "convert" and len(m_ob.jv_face_groups) >= 1: - m_ob.select = True - - bpy.ops.object.duplicate() - n_ob = context.object - n_ob.name = m_ob.name + "_cutter" - m_ob.jv_main_name = n_ob.name - - bpy.ops.object.move_to_layer(layers=[False if i < 19 else True for i in range(20)]) - context.scene.objects.active = m_ob - m_ob.select = True - - use_ob = context.object - roof_data = [use_ob.jv_object_add, use_ob.jv_roofing_types, use_ob.jv_shingle_types, - use_ob.jv_tin_roofing_types, use_ob.jv_terra_cotta_res, use_ob.jv_tile_radius] - - # if the object to use for cutting is already on the other layer - elif m_ob.jv_main_name != "none": - ft = False - use_ob = bpy.data.objects[m_ob.jv_main_name] - - # move to last layer - pre_layers = list(context.scene.layers) - context.scene.layers = [False if i < 19 else True for i in range(20)] - - use_ob.select = True - context.scene.objects.active = use_ob - bpy.ops.object.duplicate() # leave duplicate on last layer, move back original - dup_ob = context.object - context.scene.objects.active = use_ob - use_ob.select = True - dup_ob.select = False - - context.scene.layers = pre_layers - - # move cutter object to active layer - al = context.scene.active_layer - context.scene.layers = [False if i < 19 else True for i in range(20)] # just last layer - - layer_list = [False] * 19 - layer_list.insert(al, True) - - bpy.ops.object.move_to_layer(layers=layer_list) - context.scene.layers = pre_layers - - roof_data = [m_ob.jv_object_add, m_ob.jv_roofing_types, m_ob.jv_shingle_types, m_ob.jv_tin_roofing_types, - m_ob.jv_terra_cotta_res, m_ob.jv_tile_radius] - else: - use_ob = context.object - - if use_ob.jv_object_add == "convert" and len(use_ob.jv_face_groups) >= 1: - # deselect any object that is not the active object - for ob in context.selected_objects: - if ob.name != context.object.name: - ob.select = False - - fg = [] - # create groups of face centers for cutting use the data in ob.jv_face_groups - for f_g in use_ob.jv_face_groups: - temp_l = [] - for i in f_g.data.split(","): - if int(i) < len(use_ob.data.polygons): - temp_l.append(round_tuple(tuple(use_ob.data.polygons[int(i)].center), 4)) + tx = x + for y in (0, upper_y): + verts += [ + (x+qi, y, qi), + (x+hi, y, hi), + (x, y, hi), + (x, y, sqi), + (x+fei, y, sqi), + (x+fei, y, 0) + ] + + x += fei + width + verts += [ + (x, y, 0), + (x, y, tei), + (x-qi, y, tei), + (x-qi, y, sei), + (x-hi, y, fei) + ] + x = tx # reset back to beginning + + x += fei + width - nsi # move to next pan + + for i in range(v_offset-1): # one less face than the number of vertices in pan + faces.append((p+i, p+i+1, p+i+1+v_offset, p+i+v_offset)) + + @staticmethod + def _shingles_3_tab(dims: tuple, props, verts, faces): + width, exposure, th, gap = Units.FOOT, 11*Units.H_INCH, props.thickness_thin, Units.H_INCH + + first_length_for_fixed_offset = (width - (gap/2)) * (props.row_offset / 100) + if first_length_for_fixed_offset == 0: + first_length_for_fixed_offset = width - (gap/2) + + offset_length_variance = JVRoofing._create_variance_function(props.vary_row_offset, width / 2, + props.row_offset_variance) + + # there are three layers for the last bit of the shingle, so 1 1/2 up needs to be at 2th + bottom_z = (width / (2*exposure)) * 2 * th + middle_z = bottom_z - th + + # bottom backing row + verts += [ + (0, 0, bottom_z-th), + (props.length, 0, bottom_z-th), + (props.length, exposure, middle_z-th), + (0, exposure, middle_z-th) + ] + faces.append((0, 3, 2, 1)) + + upper_x, upper_y = dims + y = 0 + odd = False + while y < upper_y: + x = 0 + p = len(verts) + is_gap = False + while x < upper_x + width: # go farther to ensure that last set of vertices is placed + verts += [ + (x, y, bottom_z), + (x, y+exposure, middle_z), + (x, y+width, 0) + ] + + if is_gap: + x += gap else: - self.report({"ERROR"}, "JARCH Vis: Cannot Find Face, Please Update Roof Face Groups") - - temp_m = [temp_l, f_g.face_slope, f_g.rot] - fg.append(temp_m) - - # split object - # deselect all faces and edges to make sure no extra geometry gets separated - for f in use_ob.data.polygons: - f.select = False - for e in use_ob.data.edges: - e.select = False - for v in use_ob.data.vertices: - v.select = False - - bpy.ops.object.editmode_toggle() - bpy.ops.object.editmode_toggle() - - # select faces and separate by selection - # remove first object from list and apply it to the main object - main_data = fg[0] - del fg[0] - - for i in fg: - for i2 in i[0]: - for face_in_obj in use_ob.data.polygons: - if round_tuple(tuple(face_in_obj.center), 4) == i2: - face_in_obj.select = True - bpy.ops.object.editmode_toggle() - bpy.ops.mesh.separate(type="SELECTED") - bpy.ops.object.editmode_toggle() - - # set newly created plane objects jv_pl_z_rot and jv_pl_pitch for use when creating roofing for it - temp_ob = bpy.context.selected_objects[0] - temp_ob.jv_pl_pitch = i[1] - temp_ob.jv_pl_z_rot = i[2] - - use_ob.jv_pl_pitch = main_data[1] - use_ob.jv_pl_z_rot = main_data[2] - - # create list of objects and solidify current ones - for ob in context.selected_objects: - obs.append(ob.name) - context.scene.objects.active = ob - bpy.ops.object.modifier_add(type="SOLIDIFY") - context.object.modifiers["Solidify"].thickness = 1 - context.object.modifiers["Solidify"].offset = 0.0 - - roofing_names = [] - main_name = "" - # for each cutter object - if use_ob.jv_object_add == "convert" and obs != []: - for i in context.selected_objects: - i.select = False - - # get rid of extra vertices in main object - index_list = [] - for fa in use_ob.data.polygons: - for vt in fa.vertices: - if vt not in index_list: - index_list.append(vt) - for vt in use_ob.data.polygons: - if vt.index not in index_list: - vt.select = True - - use_ob.select = True - context.scene.objects.active = use_ob - bpy.ops.object.editmode_toggle() - bpy.ops.mesh.delete(type="VERT") - bpy.ops.object.editmode_toggle() - use_ob.select = False - - for o_name in obs: - o = bpy.data.objects[o_name] - - # calculate size and then create siding and cut it - xy_dim, z = object_dimensions(o, o.jv_pl_z_rot) - z_dim = 2 * z - - # create roofing object - new_ob = create_roofing(context, roof_data[0], roof_data[1], roof_data[2], roof_data[3], xy_dim, - z_dim, o.jv_pl_pitch, roof_data[4], roof_data[5]) - - # center o's origin point, use BMesh method for better results - o.select = True - context.scene.objects.active = o - bm = bmesh.new() - bm.from_mesh(o.data) - bm.faces.ensure_lookup_table() - - center = Vector((0, 0, 0)) - for face in bm.faces: - center += face.calc_center_bounds() - center = o.matrix_world * (center / len(bm.faces)) - cr = context.scene.cursor_location.copy() - context.scene.cursor_location = center - bpy.ops.object.origin_set(type="ORIGIN_CURSOR") - context.scene.cursor_location = cr - - o.select = False - # set new object's location and rotation - new_ob.select = True - context.scene.objects.active = new_ob - bpy.ops.object.origin_set(type="ORIGIN_CENTER_OF_MASS") - - new_ob.location = o.location - eur = Euler((0.0, 0.0, o.jv_pl_z_rot), "XYZ") - new_ob.rotation_euler = eur - - # solidify new object - if roof_data[1] == "1": - bpy.ops.object.modifier_add(type="SOLIDIFY") - new_ob.modifiers["Solidify"].thickness = 0.001 - bpy.ops.object.modifier_apply(modifier="Solidify", apply_as="DATA") - - # boolean new object - # fix normals - bpy.ops.object.modifier_add(type="BOOLEAN") - new_ob.modifiers["Boolean"].object = o - bpy.ops.object.modifier_apply(modifier="Boolean", apply_as="DATA") - - # if current object is the main object set roofing objects name to main_name - if o.name == use_ob.name: - main_name = new_ob.name - else: - roofing_names.append(new_ob.name) - - # deselect all objects - for i in context.selected_objects: - i.select = False - # get rid of extra objects and join roofing objects - for na in obs: - if na != use_ob.name: - temp_ob = bpy.data.objects[na] - temp_ob.select = True - context.scene.objects.active = temp_ob - bpy.ops.object.delete() - - last_ob = bpy.data.objects[main_name] - use_ob.data = last_ob.data - eur = Euler((0.0, 0.0, use_ob.jv_pl_z_rot), "XYZ") - use_ob.rotation_euler = eur - last_ob.select = True - context.scene.objects.active = last_ob - bpy.ops.object.delete() - - # join remaining roof objects to use_ob - for na in roofing_names: - bpy.data.objects[na].select = True - - use_ob.select = True - context.scene.objects.active = use_ob - bpy.ops.object.join() - bpy.ops.object.modifier_remove(modifier="Solidify") - - # if this is not the first time creating object transfer mesh to main object - if not ft: - m_ob.data = use_ob.data - bpy.ops.object.delete() - m_ob.select = True - context.scene.objects.active = m_ob - dup_ob.name = m_ob.name + "_cutter" - - # uvs and materials - if m_ob.jv_is_unwrap: - unwrap_object(self, context) - if m_ob.jv_is_random_uv: - random_uvs(self, context) - - for i in materials: - mat = bpy.data.materials[i] - m_ob.data.materials.append(mat) - - # remove random vertices that aren't apart of any faces, seem to come from boolean process - bpy.ops.object.editmode_toggle() - bm = bmesh.from_edit_mesh(context.object.data) - to_remove = [] - for v in bm.verts: - if len(v.link_faces) == 0: - to_remove.append(v) - for v in to_remove: - bm.verts.remove(v) - bmesh.update_edit_mesh(context.object.data) - bpy.ops.object.editmode_toggle() - - # if object is added, create mesh - if m_ob.jv_object_add == "add": - for i in m_ob.data.materials: - materials.append(i.name) - - # create new object - create_roofing(context, m_ob.jv_object_add, m_ob.jv_roofing_types, m_ob.jv_shingle_types, - m_ob.jv_tin_roofing_types, m_ob.jv_over_length, m_ob.jv_over_width, m_ob.jv_slope, - m_ob.jv_terra_cotta_res, m_ob.jv_tile_radius) - - for i in materials: - mat = bpy.data.materials[i] - m_ob.data.materials.append(mat) - - if m_ob.jv_is_unwrap: - unwrap_object(self, context) - if m_ob.jv_is_random_uv: - random_uvs(self, context) - - # create mirrored object - if m_ob.jv_is_mirrored: - bpy.ops.object.modifier_add(type="MIRROR") - context.object.modifiers["Mirror"].use_x = False - context.object.modifiers["Mirror"].use_y = True - bpy.ops.object.modifier_apply(apply_as="DATA", modifier="Mirror") - - # reselect objects - for i in sel: - bpy.data.objects[i].select = True - - -def roofing_materials(self): - # create material - o = bpy.context.object - - # check to make sure pictures have been picked - if o.jv_col_image == "" and o.jv_roofing_types in ("2", "3"): - self.report({"ERROR"}, "JARCH Vis: No Color Image Filepath") - return - if o.jv_is_bump and o.jv_norm_image == "" and o.jv_roofing_types in ("2", "3"): - self.report({"ERROR"}, "JARCH Vis: No Normal Image Filepath") - return - - if o.jv_roofing_types == "1": - mat = glossy_diffuse_material(bpy, o.jv_tin_color, (1.0, 1.0, 1.0), 0.18, 0.05, "roofing_use") - else: - mat = image_material(bpy, o.jv_im_scale, o.jv_col_image, o.jv_norm_image, o.jv_bump_amo, o.jv_is_bump, - "roofing_use", True, 0.1, 0.05, o.jv_is_rotate, None) - - if mat is not None: - if len(o.data.materials) == 0: - o.data.materials.append(mat.copy()) - else: - o.data.materials[0] = mat.copy() - o.data.materials[0].name = "roofing_"+o.name - else: - self.report({"ERROR"}, "JARCH Vis: Image(s) Not Found, Make Sure Path Is Correct") - - # remove extra materials - for i in bpy.data.materials: - if i.users == 0: - bpy.data.materials.remove(i) - - -def collect_item_data(self, context, ignore): - face_indices = [] - ob = context.object - - # toggle edit-mode to update which edges are selected - bpy.ops.object.editmode_toggle() - bpy.ops.object.editmode_toggle() - - selected_faces = [] - # create list of selected edges - for fa in ob.data.polygons: - if fa.select: - # make sure there are not duplicate faces, if there are, report error and exit - for fg in ob.jv_face_groups: - if str(fa.index) not in ignore and str(fa.index) in fg.data.split(","): - self.report({"ERROR"}, "JARCH Vis: Face Is Already In Another Group") - return None, None - - selected_faces.append(fa) - face_indices.append(str(fa.index)) - - return selected_faces, face_indices - - -def add_item(self, context): - ob = context.object - selected_faces, face_indices = collect_item_data(self, context, []) - - if face_indices is not None and len(face_indices) > 0: # make sure a face is selected - item = ob.jv_face_groups.add() - - # set collection object item data - item.data = ",".join(face_indices) - item.num_faces = len(face_indices) - - # calculate slope and rotation - rot = rot_from_normal(selected_faces[0].normal) - item.face_slope = 12 * tan(rot[1]) - item.rot = rot[2] - radians(270) - - item.name = "Group " + str(ob.jv_face_group_ct + 1) - - ob.jv_face_group_index = len(ob.jv_face_groups) - 1 - ob.jv_face_group_ct = len(ob.jv_face_groups) - elif face_indices is not None and len(face_indices) == 0: - self.report({"ERROR"}, "JARCH Vis: At Least One Face Must Be Selected") - - -def update_item(self, context): - ob = context.object - - if len(ob.jv_face_groups) > 0: - fg = ob.jv_face_groups[ob.jv_face_group_index] - selected_faces, face_indices = collect_item_data(self, context, fg.data.split(",")) - - if len(face_indices) > 0: - # set collection object item data - fg.data = ",".join(face_indices) - fg.num_faces = len(face_indices) - - # get slope and rot - rot = rot_from_normal(selected_faces[0].normal) - fg.face_slope = 12 * tan(rot[1]) - fg.rot = rot[2] - radians(270) - else: - self.report({"ERROR"}, "JARCH Vis: At Least One Face Must Be Selected") - - -def update_all_items(self, context): - ob = context.object - bpy.ops.object.editmode_toggle() - bpy.ops.object.editmode_toggle() - - for fg in ob.jv_face_groups: - fa = ob.data.polygons[int(fg.data.split(",")[0])] - rot = rot_from_normal(fa.normal) - fg.face_slope = 12 * tan(rot[1]) - fg.rot = rot[2] - radians(270) - - -class RoofingPanel(bpy.types.Panel): - bl_idname = "OBJECT_PT_jv_roofing" - bl_label = "JARCH Vis: Roofing" - bl_space_type = "VIEW_3D" - bl_region_type = "TOOLS" - bl_category = "JARCH Vis" - - def draw(self, context): - layout = self.layout - ob = context.object - - # if in edit mode layout UIlist - if ob is not None: - if ob.jv_internal_type in ("roofing", "") and ob.type == "MESH": - if context.mode == "EDIT_MESH" and ob.jv_object_add == "none": - layout.template_list("OBJECT_UL_jv_face_groups", "", ob, "jv_face_groups", ob, - "jv_face_group_index") - layout.separator() - row = layout.row() - row.operator("mesh.jv_add_face_group_item", icon="ZOOMIN") - row.operator("mesh.jv_remove_face_group_item", icon="ZOOMOUT") - row = layout.row() - row.operator("mesh.jv_update_face_group_item", icon="FILE_REFRESH") - row.operator("mesh.jv_update_face_group_items", icon="FILE_REFRESH") - - # let user know if all faces are in a group - ct = 0 - for fg in ob.jv_face_groups: - ct += fg.num_faces - if ct == len(ob.data.polygons): - layout.label("Every Face Is In A Group", icon="INFO") - - elif context.mode == "EDIT_MESH" and ob.jv_object_add != "none": - layout.label("This Object Is Already A JARCH Vis: Roofing Object", icon="INFO") - - # if in object mode and there are face groups - if (context.mode == "OBJECT" and len(ob.jv_face_groups) >= 1 and ob.jv_object_add == "convert") or \ - ob.jv_object_add == "add": - - if True: # ob.jv_object_add != "convert": - layout.prop(ob, "jv_roofing_types", icon="MATERIAL") + if x == 0: + if odd and not props.vary_row_offset: + x += first_length_for_fixed_offset + elif props.vary_row_offset: + x += offset_length_variance() + else: + x += width - (gap / 2) else: - layout.label("Material: Tin", icon="MATERIAL") - - if ob.jv_roofing_types == "1": - layout.prop(ob, "jv_tin_roofing_types") - elif ob.jv_roofing_types == "2": - layout.prop(ob, "jv_shingle_types") - - layout.separator() - if ob.jv_object_add != "convert": - layout.prop(ob, "jv_over_length") - layout.prop(ob, "jv_over_width") - layout.prop(ob, "jv_slope") - layout.separator() - layout.prop(ob, "jv_is_mirrored", icon="MOD_MIRROR") - - layout.separator() - if ob.jv_roofing_types == "3": - layout.prop(ob, "jv_tile_radius") - layout.prop(ob, "jv_terra_cotta_res") - layout.separator() - - # uv stuff - layout.prop(ob, "jv_is_unwrap", icon="GROUP_UVS") - layout.prop(ob, "jv_is_random_uv", icon="RNDCURVE") - - # materials - layout.separator() - if context.scene.render.engine == "CYCLES": - layout.prop(ob, "jv_is_material", icon="MATERIAL") - else: - layout.label("Materials Only Supported With Cycles", icon="POTATO") - - if ob.jv_is_material and context.scene.render.engine == "CYCLES": - layout.separator() - if ob.jv_roofing_types == "1": # tin - layout.prop(ob, "jv_tin_color") - elif ob.jv_roofing_types in ("2", "3"): # shingles and terra cotta - layout.prop(ob, "jv_col_image", icon="COLOR") - layout.prop(ob, "jv_is_bump", icon="SMOOTHCURVE") - - if ob.jv_is_bump: - layout.prop(ob, "jv_norm_image", icon="TEXTURE") - layout.prop(ob, "jv_bump_amo") - - layout.prop(ob, "jv_im_scale") - layout.prop(ob, "jv_is_rotate", icon="MAN_ROT") - - layout.separator() - layout.operator("mesh.jv_roofing_materials", icon="MATERIAL") - layout.prop(ob, "jv_is_preview", icon="SCENE") - - # operators - layout.separator() - layout.operator("mesh.jv_roofing_update", icon="FILE_REFRESH") - layout.operator("mesh.jv_roofing_delete", icon="CANCEL") - layout.operator("mesh.jv_roofing_add", icon="LINCURVE") - elif ob.jv_object_add == "none" and context.mode == "OBJECT" and len(ob.jv_face_groups) == 0: - layout.label("Enter Edit Mode And Create Face Groups", icon="ERROR") - layout.operator("mesh.jv_roofing_add", icon="LINCURVE") - elif ob.jv_object_add == "none" and context.mode == "OBJECT" and len(ob.jv_face_groups) >= 1: - layout.operator("mesh.jv_roofing_convert", icon="FILE_REFRESH") - layout.operator("mesh.jv_roofing_add", icon="LINCURVE") - else: - if ob.type != "MESH": - layout.label("Only Mesh Objects Can Be Used", icon="ERROR") + x += width - (gap / 2) + + is_gap = not is_gap + odd = not odd + y += exposure + + # faces, connect in two possible ways, depending on whether it is a gap or not + sets = (len(verts) - p) // 3 # each set contains 3 vertices + is_gap = False + for i in range(0, 3*(sets - 1), 3): # do one less set to just fill between sets + if is_gap: + faces.append((p+i+1, p+i+2, p+i+5, p+i+4)) else: - layout.label("This Is Already A JARCH Vis Object", icon="INFO") - layout.operator("mesh.jv_roofing_add", icon="LINCURVE") - else: - layout.operator("mesh.jv_roofing_add", icon="LINCURVE") - - -class OBJECT_UL_jv_face_groups(bpy.types.UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): - row = layout.row(align=True) - row.prop(item, "name", text="", emboss=False, translate=False, icon="FACESEL") - row.label("Faces: "+str(item.num_faces)) - row.label("Pitch: "+str(round(item.face_slope, 3))) - row.label("Rot: "+str(round(degrees(item.rot), 1))) - - -class RoofingUpdate(bpy.types.Operator): - bl_idname = "mesh.jv_roofing_update" - bl_label = "Update Roofing" - bl_description = "Update Roofing" - bl_options = {"UNDO", "INTERNAL"} - - def execute(self, context): - update_roofing(self, context) - return {"FINISHED"} - - -class RoofingDelete(bpy.types.Operator): - bl_idname = "mesh.jv_roofing_delete" - bl_label = "Delete Roofing" - bl_options = {"UNDO", "INTERNAL"} - - def execute(self, context): - o = context.object - # deselect any selected objects - for i in context.selected_objects: - i.select = False - - if o.jv_object_add == "add": - o.select = True - bpy.ops.object.delete() - - elif o.jv_object_add == "convert": - second_obj = bpy.data.objects[o.jv_main_name] - - if second_obj is not None: - o.select = False - second_obj.select = True - context.scene.objects.active = second_obj - - # move to last layer, move that object back, then move back - first_layers = [i for i in bpy.context.scene.layers] - move_layers = [False] * 19 - move_layers.append(True) - - bpy.context.scene.layers = move_layers - bpy.ops.object.move_to_layer(layers=first_layers) - bpy.context.scene.layers = first_layers - - second_obj.jv_object_add = "none" - second_obj.jv_internal_type = "" - # remove "_cutter" from name - if second_obj.name[len(second_obj.name)-7:len(second_obj.name)] == "_cutter": - second_obj.name = second_obj.name[0:len(second_obj.name)-7] - second_obj.select = False - - o.select = True - context.scene.objects.active = o - bpy.ops.object.delete() - - return {"FINISHED"} - - -class RoofingMaterials(bpy.types.Operator): - bl_idname = "mesh.jv_roofing_materials" - bl_label = "Generate\\Update Materials" - bl_options = {"UNDO", "INTERNAL"} - - def execute(self, context): - roofing_materials(self) - return {"FINISHED"} - - -class RoofingAdd(bpy.types.Operator): - bl_idname = "mesh.jv_roofing_add" - bl_label = "Add Roofing" - bl_description = "JARCH Vis: Roofing Generator" - - @classmethod - def poll(cls, context): - return context.mode == "OBJECT" - - def execute(self, context): - bpy.ops.mesh.primitive_cube_add() - o = bpy.context.scene.objects.active - o.jv_internal_type = "roofing" - o.jv_object_add = "add" - return {"FINISHED"} - - -class RoofingConvert(bpy.types.Operator): - bl_idname = "mesh.jv_roofing_convert" - bl_label = "Convert To Roofing" - bl_options = {"UNDO", "INTERNAL"} - - def execute(self, context): - o = context.object - o.jv_internal_type = "roofing" - o.jv_object_add = "convert" - return {"FINISHED"} - - -class FGAddItem(bpy.types.Operator): - bl_idname = "mesh.jv_add_face_group_item" - bl_label = "Add" - bl_options = {"UNDO", "INTERNAL"} - - def execute(self, context): - add_item(self, context) - return {"FINISHED"} - - -class FGRemoveItem(bpy.types.Operator): - bl_idname = "mesh.jv_remove_face_group_item" - bl_label = "Remove" - bl_options = {"UNDO", "INTERNAL"} - - def execute(self, context): - ob = context.object - if len(ob.jv_face_groups) > 0: - ob.jv_face_groups.remove(ob.jv_face_group_index) - ob.jv_face_group_ct = len(ob.jv_face_groups) - - # update names - for i in range(ob.jv_face_group_index, len(ob.jv_face_groups), 1): - ob.jv_face_groups[i].name = "Group " + str(i + 1) - - if len(ob.jv_face_groups) == 0 or ob.jv_face_group_index <= 0: - ob.jv_face_group_index = 0 - else: - ob.jv_face_group_index -= 1 - return {"FINISHED"} + faces.extend(((p+i, p+i+1, p+i+4, p+i+3), (p+i+1, p+i+2, p+i+5, p+i+4))) + is_gap = not is_gap -class FGUpdateItem(bpy.types.Operator): - bl_idname = "mesh.jv_update_face_group_item" - bl_label = "Update" - bl_options = {"UNDO", "INTERNAL"} - - def execute(self, context): - update_item(self, context) - return {"FINISHED"} + @staticmethod + def _shingles_architectural(dims: tuple, props, verts, faces): + hi, th, width = Units.H_INCH, props.thickness_thin, Units.FOOT + hw = width / 2 + bottom_z, mid_z = 4*th, 2*th -class FGUpdateAllItems(bpy.types.Operator): - bl_idname = "mesh.jv_update_face_group_items" - bl_label = "Update All" - bl_options = {"UNDO", "INTERNAL"} + separation_variance = JVRoofing._create_variance_function(True, 8*Units.INCH, 40) + width_variance = JVRoofing._create_variance_function(True, 4*Units.INCH, 60) - def execute(self, context): - update_all_items(self, context) - return {"FINISHED"} + upper_x, upper_y = dims + y = 0 + odd = False + while y < upper_y: + # row backing layer + verts += [ + (0, y, bottom_z), + (upper_x, y, bottom_z), + (upper_x, y+width, 0), + (0, y+width, 0) + ] + p = len(verts) - 4 + faces.append((p, p+3, p+2, p+1)) -# class FaceGroup(bpy.types.PropertyGroup): -# data = StringProperty() -# num_faces = IntProperty() -# face_slope = FloatProperty() -# rot = FloatProperty(unit="ROTATION") -# -# -def register(): - wm = bpy.context.window_manager - km = wm.keyconfigs.addon.keymaps.new(name="3D View", space_type="VIEW_3D") - km.keymap_items.new("mesh.jv_add_face_group_item", "A", "PRESS", ctrl=True) - bpy.utils.register_module(__name__) - bpy.types.Object.jv_face_groups = CollectionProperty(type=FaceGroup) - - -def unregister(): - bpy.utils.unregister_module(__name__) - del bpy.types.Object.jv_face_groups - wm = bpy.context.window_manager - if wm.keyconfigs.addon: - for kmi in wm.keyconfigs.addon.keymaps['3D View'].keymap_items: - if kmi.idname == "mesh.jv_add_face_group_item": - wm.keyconfig.addon.keymaps['3D View'].keymap_items.remove(kmi) - -if __name__ == "__main__": - register() + x = 0 + finish = False + p = len(verts) + while x < upper_x or finish: + finish = False + dx = separation_variance() + if x == 0 and odd: + dx /= 2 + + verts += [ # all z's are +th because they are layered on top of the backing layer + (x, y+hw, mid_z+th), + (x, y+width, th) + ] + + x += dx + + if x < upper_x: # only do tab if we are still under width + verts += [ + (x-hi, y, bottom_z+th), + (x, y+hw, mid_z+th), + (x, y+width, th) + ] + + x += width_variance() + verts.append((x+hi, y, bottom_z+th)) + finish = True # if we get here, we need to finish the row no matter what + + # faces + # there will always be 2 verts on the end to close everything off, besides that, it will be multiple of 6 + sets = (len(verts) - p - 2) // 6 + for i in range(0, 6*sets, 6): + faces.extend(( + (p+i, p+i+1, p+i+4, p+i+3), + (p+i+2, p+i+3, p+i+6, p+i+5), + (p+i+3, p+i+4, p+i+7, p+i+6) + )) + + y += hw + odd = not odd + + @staticmethod + def _shakes(dims: tuple, props, verts, faces): + length, width, gap = props.shake_length, props.shake_width, props.gap_uniform + th_z, hl = 2 * props.thickness_thin, length / 2 + + first_width_for_fixed_offset = width * (props.row_offset / 100) + if first_width_for_fixed_offset == 0: + first_width_for_fixed_offset = width + + offset_width_variance = JVRoofing._create_variance_function(props.vary_row_offset, width / 2, + props.row_offset_variance) + width_variance = JVRoofing._create_variance_function(props.vary_width, width, props.width_variance) + upper_x, upper_y = dims + + # bottom row backing layer + verts += [ + (0, 0, th_z / 2), + (upper_x, 0, th_z / 2), + (upper_x, hl, 0), + (0, hl, 0) + ] + faces.append((0, 3, 2, 1)) + + y = 0 + odd = False + while y < upper_y: + x = 0 + + while x < upper_x: + cur_width = width_variance() + if x == 0: + if odd and not props.vary_row_offset: + cur_width = first_width_for_fixed_offset + elif props.vary_row_offset: + cur_width = offset_width_variance() + + dx = min(cur_width, upper_x - x) + dy = min(length, upper_y - y) + + verts += [ + (x, y, th_z), + (x + dx, y, th_z), + (x + dx, y + dy, 0), + (x, y + dy, 0) + ] + + p = len(verts) - 4 + faces.append((p, p+3, p+2, p+1)) + + x += cur_width + gap + y += hl + odd = not odd + + @staticmethod + def _terracotta(dims: tuple, props, verts, faces): + length, radius, th = props.tile_length, props.terracotta_radius, props.thickness_thin + spacing, res, hi = props.terracotta_gap, props.terracotta_resolution, Units.H_INCH + radius_small = radius - th + + # adjust by asin(th/radius) to that the half-circle doesn't overlap with the wing of the next tile + bottom_ang_step = (radians(180) - asin(th/radius)) / (res + 1) + theta = asin(th/(radius - th)) # similar to asin(th/radius) above, just for smaller radius cricle of the top + top_ang_step = radians(180) / (res + 1) # going from -theta to 180-theta, so full 180 degrees, just rotated + + upper_x, upper_y = dims + y = 0 + while y < upper_y: + x = 0 + while x < upper_x: + p = len(verts) + + # build bottom set of vertices + verts += [(x+th, y, hi+th), (x+th+hi, y, th)] + tx = x + th + hi + spacing + radius + for i in range(res+2): + ang = bottom_ang_step * i + dx, dz = radius * cos(ang), radius * sin(ang) + verts.append((tx-dx, y, th+dz)) + + # build top set of vertices + verts += [(x, y+length, hi), (x+hi, y+length, 0)] + tx = x + spacing + (4*th) + (radius - th) # adjust to center of smaller radius circle + for i in range(res+2): + ang = top_ang_step * i - theta + dx, dz = radius_small * cos(ang), radius_small * sin(ang) + verts.append((tx-dx, y+length, th+dz)) + + # faces + offset = 2 + res + 2 # 2 vertices for wing and spacing, then res+2 for half-circle + for _ in range(2 + res + 1): # 2 faces for wing and spacing, then res+1 for half-circle + faces.append((p, p+offset, p+offset+1, p+1)) + p += 1 + + # go forward to right edge of half-circle, then go back so 0.5*0.5" wing doesn't intersect half-circle + x += hi + spacing + (2*radius) - th - hi + y += length - hi # allow for 'hi' of overlap in circles diff --git a/jv_siding.py b/jv_siding.py index 30268f5..71d7e4b 100644 --- a/jv_siding.py +++ b/jv_siding.py @@ -1,2057 +1,702 @@ -# This file is part of JARCH Vis -# -# JARCH Vis is free software: you can redistribute it and/or modify +# This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # -# JARCH Vis is distributed in the hope that it will be useful, +# This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with JARCH Vis. If not, see . +# along with this program. If not, see . -from itertools import permutations +from . jv_builder_base import JVBuilderBase +from . jv_utils import Units +from math import radians, sqrt, sin, cos +import bmesh import bpy -from math import sqrt, atan, asin, sin, cos, tan -from random import uniform, choice -from mathutils import Euler, Vector -from . jv_materials import * -# import bmesh -from . jv_utils import rot_from_normal, object_dimensions, point_rotation, METRIC_INCH, METRIC_FOOT, I, HI, \ - unwrap_object, random_uvs, round_rad -from ast import literal_eval -# import jv_properties -# from bpy.props import FloatProperty, CollectionProperty - - -# manages sorting out which type of siding needs to be create, gets corner data for cutout objects -def create_siding(context, mat, jv_tin_siding_types, wood_types, vinyl_types, sloped, ow, oh, bw, slope, is_width_vary, - width_vary, is_cutout, baw, spacing, is_length_vary, length_vary, max_boards, b_w, b_h, b_offset, - b_gap, b_ran_offset, b_vary, is_corner, is_invert, is_soldier, is_left, is_right, avw, avh, s_random, - b_random, x_off): - - # percentages - width_vary = 1 / (100 / width_vary) - length_vary = 1 / (100 / length_vary) - o = context.object - - # determine corner points - corner_data, corner_data_l = [], [] - if is_cutout: - for g in o.jv_cutout_groups: - corner_data.append([g.x_dist, g.z_dist, g.x_dist + g.width, g.z_dist + g.height]) - if is_soldier: - corner_data_l.append([g.x_dist, g.z_dist, g.x_dist + g.width, g.z_dist + g.height + b_w + b_gap]) - - # verts and faces - verts, faces = [], [] - v, f = [], [] - - # Wood - if mat == "1" and wood_types == "1": # Wood > Vertical - verts, faces, temp = wood_vertical(verts, faces, oh, ow, sloped, slope, is_width_vary, width_vary, bw, - spacing, is_length_vary, length_vary, max_boards) - elif mat == "1" and wood_types == "2": # Wood > Vertical: Tongue & Groove - verts, faces = wood_ton_gro(verts, faces, oh, ow, sloped, slope, bw, is_length_vary, length_vary, spacing, - max_boards) - elif mat == "1" and wood_types == "3": # Wood > Vertical: Board & Batten - verts, faces, batten_pos = wood_vertical(verts, faces, oh, ow, sloped, slope, False, width_vary, bw, 0.00635, - is_length_vary, length_vary, max_boards) - for x in batten_pos: - hw = ow - x if x + baw / 2 > ow else baw / 2 # half width of batten - p = len(v) - - z_dif = 0 - if sloped: - z_dif = slope * 2 * hw / 12 - if sloped and x > ow / 2: - z_dif *= -1 - - ht = -(slope / 12) * abs(x - hw - ow / 2) + oh if sloped else oh # height at left edge of board - ht2 = -(slope / 12) * abs(x + hw - ow / 2) + oh # height at right edge of board - - if sloped and x - hw < ow / 2 < x + hw: # middle board - v += [(x - hw, -I, 0), (x - hw, -2 * I, 0), (ow / 2, -I, 0), (ow / 2, -2 * I, 0), - (x + hw, -I, 0), (x + hw, -2 * I, 0), (x - hw, -I, ht), (x - hw, -2 * I, ht), - (ow / 2, -I, oh), (ow / 2, -2 * I, oh), (x + hw, -I, ht2), (x + hw, -2 * I, ht2)] - f += [(p, p + 2, p + 3, p + 1), (p + 2, p + 4, p + 5, p + 3), (p, p + 1, p + 7, p + 6), - (p + 1, p + 3, p + 9, p + 7), (p + 3, p + 5, p + 11, p + 9), (p + 5, p + 4, p + 10, p + 11), - (p + 2, p + 8, p + 10, p + 4), (p, p + 6, p + 8, p + 2), (p + 6, p + 7, p + 9, p + 8), - (p + 9, p + 11, p + 10, p + 8)] - else: - v += [(x - hw, -I, 0), (x - hw, -2 * I, 0), (x + hw, -I, 0), (x + hw, -2 * I, 0), - (x - hw, -I, ht), (x - hw, -2 * I, ht), (x + hw, -I, ht + z_dif), (x + hw, -2 * I, ht + z_dif)] - f += [(p, p + 2, p + 3, p + 1), (p, p + 1, p + 5, p + 4), (p + 1, p + 3, p + 7, p + 5), - (p + 3, p + 2, p + 6, p + 7), (p, p + 4, p + 6, p + 2), (p + 5, p + 7, p + 6, p + 4)] - elif mat == "1" and wood_types == "4": - verts, faces = wood_lap(oh, ow, sloped, slope, bw, verts, faces, is_length_vary, length_vary, max_boards, - 0.02540, spacing) - elif mat == "1" and wood_types == "5": - verts, faces = wood_lap_bevel(oh, ow, sloped, slope, bw, is_length_vary, length_vary, faces, verts, - max_boards, spacing) - # Vinyl - elif mat == "2" and vinyl_types == "1": - verts, faces = vinyl_vertical(oh, ow, sloped, slope, is_length_vary, length_vary, bw, baw, faces, verts, - max_boards, spacing) - elif mat == "2" and vinyl_types == "2": - verts, faces = vinyl_lap(oh, ow, sloped, slope, is_length_vary, length_vary, bw, faces, verts, max_boards, - spacing) - elif mat == "2" and vinyl_types == "3": - verts, faces = vinyl_dutch_lap(oh, ow, sloped, slope, is_length_vary, length_vary, bw, faces, verts, - max_boards, spacing) - # Tin - elif mat == "3" and jv_tin_siding_types == "1": - verts, faces = tin_normal(oh, ow, sloped, slope, faces, verts) - elif mat == "3" and jv_tin_siding_types == "2": - verts, faces = tin_angular(oh, ow, sloped, slope, faces, verts) - # Fiber Cement - elif mat == "4": - verts, faces = wood_lap(oh, ow, sloped, slope, bw, verts, faces, is_length_vary, length_vary, max_boards, - 0.009525, spacing) - # Bricks - elif mat == "5": - verts, faces = bricks(oh, ow, b_w, b_h, b_offset, b_gap, b_ran_offset, b_vary, faces, verts, - is_corner, is_invert, is_left, is_right) - # Stone - elif mat == "6": - verts, faces = stone_faces(oh, ow, avw, avh, s_random, b_random, b_gap) - - # adjust for x_offset - out_verts, out_v = [], [] - - for i in verts: - l = list(i) - l[0] += x_off - out_verts.append(l) - for i in v: - l = list(i) - l[0] += x_off - out_v.append(l) - - return out_verts, faces, corner_data, corner_data_l, out_v, f - - -def boolean_object(corner_data): # creates list of vertices and faces for all the cutout objects - verts, faces = [], [] - for ob in corner_data: - p = len(verts) - verts += [(ob[0], 0.5, ob[1]), (ob[0], -0.5, ob[1]), (ob[2], -0.5, ob[1]), (ob[2], 0.5, ob[1]), - (ob[0], 0.5, ob[3]), (ob[0], -0.5, ob[3]), (ob[2], -0.5, ob[3]), (ob[2], 0.5, ob[3])] - faces += [(p, p + 3, p + 2, p + 1), (p + 4, p + 5, p + 6, p + 7), (p, p + 1, p + 5, p + 4), - (p + 2, p + 3, p + 7, p + 6), (p, p + 4, p + 7, p + 3), (p + 1, p + 2, p + 6, p + 5)] - return verts, faces - - -def slope_height(x, slope, ow, oh): - return -(slope / 12) * abs(x - ow / 2) + oh - - -def recalculate_slope(slope, oh, ow): - square = oh - ((slope * (ow / 2)) / 12) # height - what that slope and width would give for height - if square <= 0: # check is it is a negative number - slope = ((24 * oh) / ow) - 0.01 - - return slope - - -def wood_vertical(verts, faces, oh, ow, sloped, slope, is_width_vary, width_vary, width, spacing, is_length_vary, - length_vary, max_boards): - cur_x = 0.0 - batten_pos = [] - if sloped: - slope = recalculate_slope(slope, oh, ow) - - while cur_x < ow: - if is_length_vary: - v = oh * length_vary * 0.45 - bl = uniform(oh / 2 - v, oh / 2 + v) - else: - bl = oh - max_boards = 1 - - if is_width_vary: - v = width * width_vary * 0.75 - bw = uniform(width - v, width + v) - else: - bw = width - - if cur_x + bw > ow: # trim if wider - bw = ow - cur_x - - z_dif = 0 - if sloped: - z_dif = slope * bw / 12 - if sloped and cur_x > ow / 2: - z_dif *= -1 - - ct = 1 - cur_z = 0 - while cur_z < oh: - center = True - p = len(verts) - ht = -(slope / 12) * abs(cur_x - ow / 2) + oh if sloped else oh # height at left edge of board - ht2 = -(slope / 12) * abs(cur_x + bw - ow / 2) + oh # height at right edge of board - - if sloped and ((cur_x < ow / 2 < cur_x + bw and cur_z + bl >= oh) or - (cur_x < ow / 2 < cur_x + bw and ct >= max_boards)): # middle board - verts += [(cur_x, 0, cur_z), (cur_x, -I, cur_z), (ow / 2, 0, cur_z), (ow / 2, -I, cur_z), - (cur_x + bw, 0, cur_z), (cur_x + bw, -I, cur_z), (cur_x, 0, ht), (cur_x, -I, ht), - (ow / 2, 0, oh), (ow / 2, -I, oh), (cur_x + bw, 0, ht2), (cur_x + bw, -I, ht2)] - cur_z = oh - center = False - elif cur_z + bl >= ht or ct >= max_boards or (cur_x > ow / 2 and cur_z + bl > ht2): - verts += [(cur_x, 0, cur_z), (cur_x, -I, cur_z), (cur_x + bw, 0, cur_z), (cur_x + bw, -I, cur_z), - (cur_x, 0, ht), (cur_x, -I, ht), (cur_x + bw, 0, ht + z_dif), (cur_x + bw, -I, ht + z_dif)] - cur_z = oh - else: - verts += [(cur_x, 0, cur_z), (cur_x, -I, cur_z), (cur_x + bw, 0, cur_z), (cur_x + bw, -I, cur_z), - (cur_x, 0, cur_z + bl), (cur_x, -I, cur_z + bl), (cur_x + bw, 0, cur_z + bl), - (cur_x + bw, -I, cur_z + bl)] - cur_z += bl + spacing - - if center: - faces += [(p, p + 2, p + 3, p + 1), (p, p + 1, p + 5, p + 4), (p + 1, p + 3, p + 7, p + 5), - (p + 3, p + 2, p + 6, p + 7), (p, p + 4, p + 6, p + 2), (p + 5, p + 7, p + 6, p + 4)] - else: - faces += [(p, p + 2, p + 3, p + 1), (p + 2, p + 4, p + 5, p + 3), (p, p + 1, p + 7, p + 6), - (p + 1, p + 3, p + 9, p + 7), (p + 3, p + 5, p + 11, p + 9), (p + 5, p + 4, p + 10, p + 11), - (p + 2, p + 8, p + 10, p + 4), (p, p + 6, p + 8, p + 2), (p + 6, p + 7, p + 9, p + 8), - (p + 9, p + 11, p + 10, p + 8)] - - ct += 1 - - batten_pos.append(cur_x + bw + spacing / 2) # middle of joint - cur_x += bw + spacing - - return verts, faces, batten_pos - - -def wood_ton_gro(verts, faces, oh, ow, sloped, slope, bw, is_length_vary, length_vary, spacing, max_boards): - x = 0.0 - tei = 0.375 / METRIC_INCH - ei = 0.125 / METRIC_INCH - ssi = 0.4375 / METRIC_INCH - qi = 0.25 / METRIC_INCH - # adjust slope if needed - if sloped: - slope = recalculate_slope(slope, oh, ow) - while x < ow: - z = 0.0 - - if x + bw > ow: - bw = ow - x - - ct = 1 - while z < oh: # while not full height - p = len(verts) - - if is_length_vary: # if varied length calculate length - v = oh * (length_vary * 0.45) - bl = uniform((oh / 2) - v, (oh / 2) + v) +class JVSiding(JVBuilderBase): + is_cutable = True + is_convertible = True + + @staticmethod + def draw(props, layout): + converted = props.convert_source_object is not None + + layout.prop(props, "siding_pattern", icon="MOD_TRIANGULATE") + + layout.separator() + row = layout.row() + row.prop(props, "height") + row.prop(props, "length") + layout.separator() + + if props.siding_pattern in ("regular", "shiplap", "dutch_lap", "clapboard"): + row = layout.row() + row.prop(props, "board_width_medium") + row.prop(props, "board_length_long") + elif props.siding_pattern == "brick": + row = layout.row() + row.prop(props, "brick_height") + row.prop(props, "brick_length") + elif props.siding_pattern in ("shakes", "scallop_shakes"): + row = layout.row() + row.prop(props, "shake_length") + row.prop(props, "shake_width") + + if props.siding_pattern in ("regular", "shiplap"): + layout.separator() + layout.prop(props, "orientation", icon="ORIENTATION_VIEW") + + if props.siding_pattern == "dutch_lap": + layout.separator() + layout.prop(props, "dutch_lap_breakpoint") + + # width variance + if props.siding_pattern in ("regular", "shiplap", "dutch_lap", "clapboard", "shakes"): + layout.separator() + row = layout.row() + + row.prop(props, "vary_width", icon="RNDCURVE") + if props.vary_width: + row.prop(props, "width_variance") + + # length variance + if props.siding_pattern in ("regular", "shiplap", "dutch_lap", "clapboard"): + layout.separator() + row = layout.row() + + row.prop(props, "vary_length", icon="RNDCURVE") + if props.vary_length: + row.prop(props, "length_variance") + + # scallop resolution + if props.siding_pattern == "scallop_shakes": + layout.separator() + layout.prop(props, "scallop_resolution") + + # battens + if props.siding_pattern == "regular" and props.orientation == "vertical": + layout.separator() + box = layout.box() + box.prop(props, "battens", icon="SNAP_EDGE") + + if props.battens: + box.separator() + box.prop(props, "batten_width") + + row = box.row() + row.prop(props, "vary_batten_width", icon="RNDCURVE") + if props.vary_batten_width: + row.prop(props, "batten_width_variance") + + # jointing + if not converted and props.siding_pattern == "brick": + layout.separator() + row = layout.row() + row.prop(props, "joint_left", icon="ALIGN_RIGHT") + row.prop(props, "joint_right", icon="ALIGN_LEFT") + + # row offset + if props.siding_pattern in ("shakes", "scallop_shakes") or \ + (props.siding_pattern == "brick" and not props.joint_right and not props.joint_left): + layout.separator() + row = layout.row() + + row.prop(props, "vary_row_offset", icon="RNDCURVE") + if props.vary_row_offset: + row.prop(props, "row_offset_variance") else: - bl = oh + row.prop(props, "row_offset") - if z + bl + spacing > oh or (is_length_vary and ct >= max_boards): - bl = oh - z + # thickness and variance + if props.siding_pattern not in ("tin_regular", "tin_angular"): + layout.separator() + box = layout.box() - if sloped and ((z + bl + spacing > slope_height(x, slope, ow, oh) and x < ow / 2) or - (z + bl + spacing > slope_height(x + bw + HI, slope, ow, oh) and x > ow / 2)): - h1, h2, h3, h4, h5, h6, h7 = [slope_height(i, slope, ow, oh) for i in - [x, x + qi, x + HI, x + bw / 2, x + bw, x + bw + qi, x + bw + HI]] - bl = oh # force z to be greater then oh on update + if props.siding_pattern == "brick": + box.prop(props, "thickness_thick") + elif props.siding_pattern in ("clapboard", "shakes", "scallop_shakes"): + box.prop(props, "thickness_thin") else: - h1, h2, h3, h4, h5, h6, h7 = [z + bl] * 7 - - verts += [[x, 0, z], [x, 0, h1], [x, -tei, z], [x, -tei, h1], [x + qi, -tei, z], [x + qi, -tei, h2], - [x + HI, -tei, z], [x + HI, -tei, h3], [x + HI, -tei - qi, z], [x + HI, -tei - qi, h3], - [x + qi, -tei - qi, z], [x + qi, -tei - qi, h2], [x, -tei - qi, z], [x, -tei - qi, h1], - [x, -I, z], [x, -I, h1], [x + qi, -I, z], [x + qi, -I, h2], [x + HI, -I, z], [x + HI, -I, h3], - [x + bw / 2, -I, z], [x + bw / 2, -I, h4], [x + bw, -I, z], [x + bw, -I, h5], - [x + bw, -ssi - ei, z], [x + bw, -ssi - ei, h5], [x + bw + qi, -ssi - ei, z], - [x + bw + qi, -ssi - ei, h6], [x + bw + HI, -ssi - ei, z], [x + bw + HI, -ssi - ei, h7], - [x + bw + HI, -ssi, z], [x + bw + HI, -ssi, h7], [x + bw + qi, -ssi, z], [x + bw + qi, -ssi, h6], - [x + bw, -ssi, z], [x + bw, -ssi, h5], [x + bw, 0, z], [x + bw, 0, h5], [x + bw / 2, 0, z], - [x + bw / 2, 0, h4], [x + HI, 0, z], [x + HI, 0, h3], [x + qi, 0, z], [x + qi, 0, h2], - [x + bw / 2, -tei, z], [x + bw / 2, -tei, h4], [x + bw / 2, -tei - qi, z], - [x + bw / 2, -tei - qi, h4]] - - if sloped and ((x < ow / 2 < x + bw + HI and z + bl >= oh) or - (x < ow / 2 < x + bw + HI and ct >= max_boards)): - if x + HI > ow / 2: # in groove - adj = [4, 5, 10, 11, 16, 17, 42, 43] - elif x + bw > ow / 2: # in main board - adj = [20, 21, 38, 39, 44, 45, 46, 47] - else: # in tongue - adj = [26, 27, 32, 33] - - for i in adj: # adjust to make points at proper x and z values - verts[p + i][0] = ow / 2 - if i % 2 != 0: - verts[p + i][2] = oh - - p2 = p - for i in range(20): - faces += [(p2, p2 + 2, p2 + 3, p2 + 1)] - p2 += 2 - faces += [(p, p + 1, p2 + 1, p2)] # last face - - faces += [(p, p + 42, p + 4, p + 2), (p + 4, p + 42, p + 40, p + 6), (p + 6, p + 40, p + 38, p + 44), - (p + 34, p + 44, p + 38, p + 36), (p + 6, p + 44, p + 46, p + 8), (p + 24, p + 46, p + 44, p+34), - (p + 24, p + 34, p + 32, p + 26), (p + 26, p + 32, p + 30, p + 28), (p + 10, p + 16, p+14, p+12), - (p + 8, p + 18, p + 16, p + 10), (p + 8, p + 46, p + 20, p + 18), (p + 20, p + 46, p + 24, p+22), - (p + 1, p + 3, p + 5, p + 43), (p + 5, p + 7, p + 41, p + 43), (p + 7, p + 45, p + 39, p + 41), - (p + 39, p + 45, p + 35, p + 37), (p + 7, p + 9, p + 47, p + 45), (p + 45, p + 47, p + 25, p+35), - (p + 35, p + 25, p + 27, p + 33), (p + 33, p + 27, p + 29, p + 31), (p + 11, p + 13, p+15, p+17), - (p + 9, p + 11, p + 17, p + 19), (p + 9, p + 19, p + 21, p + 47), (p + 47, p + 21, p + 23, p+25)] - - ct += 1 - z += bl + spacing - x += bw + spacing - - return verts, faces - - -def wood_lap(oh, ow, sloped, slope, bw, verts, faces, is_length_vary, length_vary, max_boards, thickness, spacing): - z = 0.0 - th = thickness - - if sloped: # if slope check and see if slope is possible, if not recalculate - slope = recalculate_slope(slope, oh, ow) - - start_x = 0.0 # used to jump start cur_x if sloped - last_x = ow - square = oh - (ow / 2) * (slope / 12) - theta = asin(th / (bw - I)) - inch_offset = I * cos(theta) * (12 / slope) - - while z < oh: - ct = 1 - y_dif = 0 - x = start_x - - if z + bw * cos(theta) > oh and not sloped: - temp = bw - bw = sqrt(th**2 + (oh - z)**2) + I - y_dif = (temp - bw) * sin(theta) # help keep board oriented well - - while x < last_x: - p = len(verts) - normal_face = True - - if is_length_vary: - v = ow * (length_vary * 0.49) - bl = uniform((ow / 2) - v, (ow / 2) + v) - if sloped and bl < bw * cos(theta) * (12 / slope): # make board longer then sloped part of board - bl = bw * cos(theta) * (12 / slope) * 1.1 - else: - bl = 2 * ow - - l1, l2, l3, r1, r2, r3 = x, x, x, x + bl, x + bl, x + bl - y1, y2, y3 = y_dif + bw * sin(theta), y_dif + bw / 2 * sin(theta), y_dif - z1, z2, z3 = z, z + bw / 2 * cos(theta), z + bw * cos(theta) - - if sloped and z3 > square: - if z1 < square < z3: - z2 = square - y2 = (z3 - z2) * tan(theta) - - if x == start_x: # first - if z1 < square < z3: - l1, l2, l3 = 0, 0, (z3 - z2) * (12 / slope) - else: - l1, l2, l3 = x, x + (z2 - z) * (12 / slope), x + (z3 - z) * (12 / slope) - - start_x = l3 - inch_offset - - if x + bl >= last_x - bw * cos(theta) * (12 / slope) or (is_length_vary and ct >= max_boards): # last - if z1 < square < z3: - r1, r2, r3 = ow, ow, ow - (z3 - z2) * (12 / slope) - else: - r1, r2, r3 = last_x, last_x - (z2 - z) * (12 / slope), last_x - bw * cos(theta) * (12 / slope) - - last_x = r3 + inch_offset - bl = 2 * ow - - elif x + bl > ow or (is_length_vary and ct >= max_boards): - r1, r2, r3 = [ow] * 3 - bl = ow # make sure this is last board for row - - if z3 <= oh or (not sloped and z3 > oh): - verts += [(l1, -y1, z1), (l2, -y2, z2), (l3, -y3, z3), (l1, -y1 - th, z1), (l2, -y2 - th, z2), - (l3, -y3 - th, z3), (r1, -y1 - th, z1), (r2, -y2 - th, z2), (r3, -y3 - th, z3), - (r1, -y1, z1), (r2, -y2, z2), (r3, -y3, z3)] - else: # triangle - y2 = y1 - (oh - z) * tan(theta) - verts += [(l1, -y1, z1), (l1, -y1 - th, z1), (r1, -y1, z1), (r1, -y1 - th, z1), - (ow / 2, -y2, oh), (ow / 2, -y2 - th, oh)] - normal_face = False - - if normal_face: - p2 = p - for i in range(3): - faces += [(p2, p2 + 3, p2 + 4, p2 + 1), (p2 + 1, p2 + 4, p2 + 5, p2 + 2)] - p2 += 3 - faces += [(p + 2, p + 5, p + 8, p + 11), (p, p + 9, p + 6, p + 3), (p, p + 1, p + 10, p + 9), - (p + 1, p + 2, p + 11, p + 10)] - else: - faces += [(p, p + 2, p + 3, p + 1), (p, p + 1, p + 5, p + 4), (p + 2, p + 4, p + 5, p + 3), - (p + 1, p + 3, p + 5), (p, p + 4, p + 2)] - - ct += 1 - x += bl + spacing - - if z + bw * cos(theta) > oh: - z = oh + box.prop(props, "thickness") + + # NOTE: we cannot have varying thickness if battens are involved + if props.siding_pattern in ("dutch_lap", "brick", "shiplap") or \ + (props.siding_pattern == "regular" and not props.battens): + row = box.row() + row.prop(props, "vary_thickness", icon="RNDCURVE") + if props.vary_thickness: + row.prop(props, "thickness_variance") + + # gaps + if props.siding_pattern in ("regular", "dutch_lap", "clapboard", "brick", "shakes", "scallop_shakes"): + layout.separator() + row = layout.row() + row.prop(props, "gap_uniform") + elif props.siding_pattern == "shiplap": + layout.separator() + row = layout.row() + row.prop(props, "gap_widthwise") + row.prop(props, "gap_lengthwise") + + # grout/mortar + if props.siding_pattern == "brick": + layout.separator() + row = layout.row() + row.prop(props, "add_grout", text="Add Mortar?", icon="MOD_WIREFRAME") + if props.add_grout: + row.prop(props, "grout_depth", text="Mortar Depth") + + # slope + if not converted: + layout.separator() + box = layout.box() + row = box.row() + row.prop(props, "slope_top", icon="LINCURVE") + if props.slope_top: + row.prop(props, "pitch") + box.row().prop(props, "pitch_offset") + + @staticmethod + def update(props, context): + mortar_mesh = None + if props.convert_source_object is not None: + mesh = JVSiding._generate_mesh_from_converted_object(props, context, rot_offset=(-radians(90), 0, 0)) + + if props.siding_pattern == "brick" and props.add_grout: + mortar_mesh = JVSiding._generate_mesh_from_converted_object(props, context, + rot_offset=(-radians(90), 0, 0), + geometry_func_name="_mortar_geometry") else: - z += (bw - I) * cos(theta) - - return verts, faces - - -def wood_lap_bevel(oh, ow, sloped, slope, bw, is_length_vary, length_vary, faces, verts, max_boards, spacing): - z = 0.0 - last_x = ow - start_x = 0.0 - - if sloped: - slope = recalculate_slope(slope, oh, ow) + mesh = JVSiding._start(context) + verts, faces = JVSiding._geometry(props, (props.length, props.height)) + + mesh.clear() + for v in verts: + mesh.verts.new(v) + mesh.verts.ensure_lookup_table() + + for f in faces: + mesh.faces.new([mesh.verts[i] for i in f]) + mesh.faces.ensure_lookup_table() + + if props.siding_pattern == "brick" and props.add_grout: + mortar_mesh = bmesh.new() + JVSiding._build_mesh_from_geometry(mortar_mesh, + *JVSiding._mortar_geometry(props, (props.length, props.height))) + + # cut top and right + if props.siding_pattern in ("dutch_lap", "shiplap", "tin_regular", "tin_angular", "scallop_shakes"): + JVSiding._cut_meshes([mesh], [ + ((0, 0, props.height), (0, 0, -1)), # top + ((props.length, 0, 0), (-1, 0, 0)) # right + ]) + + # cut left + if props.siding_pattern == "scallop_shakes": + JVSiding._cut_meshes([mesh], [((0, 0, 0), (1, 0, 0))]) + + # cut slope + if props.slope_top: + meshes = [mesh] + if mortar_mesh is not None: + meshes.append(mortar_mesh) + + JVSiding._slope_top(props, meshes) + + # cutouts + if props.add_cutouts: + JVSiding._cutouts(mesh, props, context.object.matrix_world) + + if mortar_mesh is not None: + JVSiding._cutouts(mortar_mesh, props, context.object.matrix_world) + + original_edges = mesh.edges[:] + original_mortar_edges = [] if mortar_mesh is None else mortar_mesh.edges[:] + new_geometry = [] + + # solidify - add thickness to props.thickness level + if props.siding_pattern in ("regular", "brick"): + th = props.thickness_thick if props.siding_pattern == "brick" else props.thickness + new_geometry = JVSiding._solidify(mesh, + JVSiding._create_variance_function(props.vary_thickness, th, + props.thickness_variance)) + # solidify - add thickness, non-variable + elif props.siding_pattern in ("clapboard", "shakes", "scallop_shakes"): + new_geometry = JVSiding._solidify(mesh, props.thickness_thin) + + # solidify - just add slight thickness + elif props.siding_pattern == "dutch_lap": + new_geometry = JVSiding._solidify(mesh, Units.ETH_INCH) + + # add main material index + JVSiding._add_material_index(mesh.faces, 0) + + # solidify mortar + if mortar_mesh is not None: + th = props.thickness_thick * (1 - (props.grout_depth / 100)) + mortar_new_geometry = JVSiding._solidify(mortar_mesh, th) + JVSiding._add_uv_seams_for_solidified_plane(mortar_new_geometry, original_mortar_edges, mortar_mesh) + + # add uv seams + if props.siding_pattern != "shiplap": + JVSiding._add_uv_seams_for_solidified_plane(new_geometry, original_edges, mesh) + + JVSiding._finish(context, mesh) + + # merge mortar object with current object + if mortar_mesh is not None: + JVSiding._add_material_index(mortar_mesh.faces, 1) + + main_obj = context.object + bpy.ops.mesh.primitive_cube_add() + context.object.location = main_obj.location + + mortar_mesh.to_mesh(context.object.data) + main_obj.select_set(True) + context.view_layer.objects.active = main_obj + bpy.ops.object.join() # merge the objects + + JVSiding._uv_unwrap() + + @staticmethod + def _geometry(props, dims: tuple): + verts, faces = [], [] + + # dynamically call correct method as their names will match up with the style name + getattr(JVSiding, "_{}".format(props.siding_pattern))(dims, props, verts, faces) + + return verts, faces + + @staticmethod + def _regular(dims: tuple, props, verts, faces): + length, width, gap = props.board_length_long, props.board_width_medium, props.gap_uniform + batten_width, by = props.batten_width, -props.thickness + + width_variance = JVSiding._create_variance_function(props.vary_width, width, props.width_variance) + length_variance = JVSiding._create_variance_function(props.vary_length, length, props.length_variance) + batten_width_variance = JVSiding._create_variance_function(props.vary_batten_width, batten_width, + props.batten_width_variance) + upper_x, upper_z = dims + + if props.orientation == "vertical": + x = 0 + while x < upper_x: + z = 0 + + cur_width = width_variance() + while z < upper_z: + cur_length = length_variance() + + trimmed_width = min(cur_width, upper_x-x) + trimmed_length = min(cur_length, upper_z-z) + + verts += [ + (x, 0, z), + (x+trimmed_width, 0, z), + (x+trimmed_width, 0, z+trimmed_length), + (x, 0, z+trimmed_length) + ] + + p = len(verts) - 4 + faces.append((p, p+3, p+2, p+1)) + + z += cur_length + gap + x += cur_width + gap + + # battens + if props.battens and not props.vary_thickness: + bw = batten_width_variance() + tx = x - (gap / 2) - (bw / 2) + + if tx < upper_x: + bw = min(bw, upper_x-tx) + tz = 0 + while tz < upper_z: + bl = length_variance() + bl = min(bl, upper_z-tz) + + verts += [ + (tx, by, tz), + (tx+bw, by, tz), + (tx+bw, by, tz+bl), + (tx, by, tz+bl) + ] - square = oh - ((slope * (ow / 2)) / 12) + p = len(verts) - 4 + faces.append((p, p+3, p+2, p+1)) - while z < oh: - x = start_x - ct = 1 + tz += bl + gap - if z + bw > oh: - bw = oh - z - - while x < last_x: - p = len(verts) - - if is_length_vary: - v = ow * (length_vary * 0.45) - bl = uniform((ow / 2) - v, (ow / 2) + v) - if x + bl > ow or ct >= max_boards: - bl = ow - x - if sloped and x < ow / 2 and bl < bw * (12 / slope): # make sure longer than slope's run - bl = bw * (12 / slope) * 1.1 - else: - bl = ow - - sp = bw / 3 - theta = atan(I / (bw / 2)) - z1, z2, z3, z4, z5, z6, z7 = [z + i for i in [0, sp / 2, sp, 1.5 * sp, 2 * sp, 2.5 * sp, bw]] - y1, y2 = (bw / 3) * tan(theta), (bw / 6) * tan(theta) - y3, y4 = I - y2, I - y1 - - if sloped and z7 > square: - l1, l2, l3, l4, l5, l6, l7 = [x] * 7 - r1, r2, r3, r4, r5, r6, r7 = [x + bl] * 7 - - if z1 < square < z7: # adjust z positions to get row at square, adjust y positions for new z positions - if z1 < square < z3: - z2 = square - y2 = (z3 - z2) * tan(theta) - elif z3 < square < z5: - z4 = square - else: - z6 = square - y3 = I - (z6 - z5) * tan(theta) - - if x == start_x: # first - if z1 < square < z7: - temp = [z1, z2, z3, z4, z5, z6, z7] - for i in range(len(temp)): - temp[i] = (temp[i] - square) * (12 / slope) if temp[i] > square else 0 - l1, l2, l3, l4, l5, l6, l7 = temp - else: - l1, l2, l3, l4, l5, l6, l7 = [start_x + (i - z) * (12 / slope) for i in - [z1, z2, z3, z4, z5, z6, z7]] - start_x = l7 - (bw / 6) * (12 / slope) - - if x + bl >= last_x - bw * (12 / slope) or (is_length_vary and ct >= max_boards): # last - if z1 < square < z7: - temp = [z1, z2, z3, z4, z5, z6, z7] - for i in range(len(temp)): - temp[i] = ow - (temp[i] - square) * (12 / slope) if temp[i] > square else ow - r1, r2, r3, r4, r5, r6, r7 = temp - else: - r1, r2, r3, r4, r5, r6, r7 = [last_x - (i - z) * (12 / slope) for i in - [z1, z2, z3, z4, z5, z6, z7]] - - last_x = r7 + (bw / 6) * (12 / slope) - bl = 2 * ow - else: - l1, l2, l3, l4, l5, l6, l7 = [x] * 7 - r1, r2, r3, r4, r5, r6, r7 = [x + bl] * 7 - - verts += [(l1, -y1, z1), (l2, -y2, z2), (l3, 0, z3), (l4, 0, z4), (l5, 0, z5), (l6, 0, z6), (l7, 0, z7), - (l1, -I, z1), (l2, -I, z2), (l3, -I, z3), (l4, -I, z4), (l5, -I, z5), - (l6, -y3, z6), (l7, -y4, z7), (r1, -I, z1), (r2, -I, z2), (r3, -I, z3), - (r4, -I, z4), (r5, -I, z5), (r6, -y3, z6), (r7, -y4, z7), (r1, -y1, z1), (r2, -y2, z2), - (r3, 0, z3), (r4, 0, z4), (r5, 0, z5), (r6, 0, z6), (r7, 0, z7)] - - p2 = p - for i in range(3): # first three sides - for j in range(6): - faces += [(p2 + j, p2 + j + 7, p2 + j + 8, p2 + j + 1)] - p2 += 7 - for j in range(6): # back faces - faces += [(p + j, p + j + 1, p + 22 + j, p + 21 + j)] - faces += [(p + 6, p + 13, p + 20, p + 27), (p, p + 21, p + 14, p + 7)] # top and bottom - - ct += 1 - x += bl + spacing - - if z + bw >= oh: - z = oh else: - z += bw - (bw / 6) + z = 0 + while z < upper_z: + x = 0 - return verts, faces + cur_width = width_variance() + while x < upper_x: + cur_length = length_variance() + trimmed_width = min(cur_width, upper_z - z) + trimmed_length = min(cur_length, upper_x - x) -def vinyl_vertical(oh, ow, sloped, slope, is_length_vary, length_vary, bw, baw, faces, verts, max_boards, spacing): - # creates a plane, which then has a bevel and solidify added - x = 0 - pan = (bw - 2 * baw) / 2 - tei = 0.375 / METRIC_INCH - qi = 0.25 / METRIC_INCH + verts += [ + (x, 0, z), + (x + trimmed_length, 0, z), + (x + trimmed_length, 0, z+trimmed_width), + (x, 0, z+trimmed_width) + ] - if sloped: - slope = recalculate_slope(slope, oh, ow) + p = len(verts) - 4 + faces.append((p, p+3, p+2, p+1)) - while x < ow: - z = 0 - ct = 1 + x += cur_length + gap + z += cur_width + gap - while z < oh: - p = len(verts) + @staticmethod + def _dutch_lap(dims: tuple, props, verts, faces): + length, width, gap, th = props.board_length_long, props.board_width_medium, props.gap_uniform, props.thickness + break_p = props.dutch_lap_breakpoint / 100 - if is_length_vary: - v = oh * length_vary * 0.45 - bl = uniform((oh / 2) - v, (oh / 2) + v) - if z + bl + spacing > oh or ct >= max_boards: - bl = oh - z - else: - bl = oh + width_variance = JVSiding._create_variance_function(props.vary_width, width, props.width_variance) + length_variance = JVSiding._create_variance_function(props.vary_length, length, props.length_variance) + thickness_variance = JVSiding._create_variance_function(props.vary_thickness, th, props.thickness_variance) - if sloped and ((z + bl + spacing > slope_height(x, slope, ow, oh) and x < ow / 2) or - (z + bl + spacing > slope_height(x + bw, slope, ow, oh) and x > ow / 2)): - z1, z2, z3, z4, z5 = [slope_height(i, slope, ow, oh) for i in - [x, x + pan, x + pan + baw, x + bw - baw, x + bw]] - bl = oh - else: - z1, z2, z3, z4, z5 = [z + bl] * 5 - - v = [[x, -tei, z1], [x, 0, z1], [x + pan, 0, z2], [x + pan, -HI, z2], [x + pan + baw, -HI, z3], - [x + pan + baw, 0, z3], [x + bw - baw, 0, z4], [x + bw - baw, -HI, z4], [x + bw, -HI, z5], - [x + bw, -qi, z5]] - - fa = 0 - cut = False - for i in range(len(v)): # trim to correct position - # determine if middle board, make sure top is sloped, and not lower board - if sloped and 0 < i < len(v) - 1 and v[i - 1][0] < ow / 2 < v[i][0] and \ - v[i][2] == slope_height(v[i][0], slope, ow, oh): - verts += [(ow / 2, v[i - 1][1], z), (ow / 2, v[i - 1][1], oh)] - fa += 1 - - # cut for length - if v[i][0] >= ow and not cut: - if sloped and v[i][2] != v[0][2]: # sloped - v[i][2] = slope_height(ow, slope, ow, oh) - verts += [(ow, v[i][1], z), (ow, v[i][1], v[i][2])] - cut = True - fa += 1 - elif v[i][0] < ow and not cut: - verts += [(v[i][0], v[i][1], z), v[i]] - fa += 1 - - for i in range(fa - 1): - faces += [(p, p + 2, p + 3, p + 1)] - p += 2 - - ct += 1 - z += bl + spacing - x += bw - 0.125 / METRIC_INCH - - return verts, faces - - -def vinyl_lap(oh, ow, sloped, slope, is_length_vary, length_vary, bw, faces, verts, max_boards, spacing): - z = 0.0 - start_x = 0.0 - tqi = 0.01905 - last_x = ow - theta = atan(tqi / bw) - - if sloped: - slope = recalculate_slope(slope, oh, ow) - square = oh - ((slope * (ow / 2)) / 12) - else: - square = oh - - while z < oh: - x = start_x - ct = 1 - - if z + bw > oh: - bw = oh - z - - while x < last_x: - p = len(verts) - - if is_length_vary: - v = ow * length_vary * 0.49 - bl = uniform((ow / 2) - v, (ow / 2) + v) - if x + bl + spacing > ow or ct >= max_boards: - bl = ow - x - if sloped and x < ow / 2 and bl < bw * (12 / slope): # make sure longer than slope's run - bl = bw * (12 / slope) * 1.1 - else: - bl = ow - - l1, l2, l3 = [x] * 3 - r1, r2, r3 = [x + bl] * 3 - z1, z2, z3 = z, z + bw / 2, z + bw - y2, y3 = tqi / 2, 0 - - if sloped and z3 > square: - if z1 < square < z3: - z2 = square - y2 = (z3 - z2) * tan(theta) - - if x == start_x: - if z1 < square < z3: - l1, l2, l3 = x, x, x + (z3 - z2) * (12 / slope) - else: - l1, l2, l3 = x, x + (bw / 2) * (12 / slope), x + bw * (12 / slope) - start_x = l3 - if x + bl + spacing > last_x or (x + bl > ow / 2 and x + bl + spacing > last_x - bw * (12 / slope)) or \ - ct >= max_boards: - if z1 < square < z3: - r1, r2, r3 = last_x, last_x, last_x - (z3 - z2) * (12 / slope) - else: - r1, r2, r3 = last_x, last_x - (bw / 2) * (12 / slope), last_x - bw * (12 / slope) - last_x = r3 - bl = ow - - verts += [(l1, 0, z1), (l1, -tqi, z1), (l2, -y2, z2), (l3, -y3, z3), (r1, 0, z1), (r1, -tqi, z1), - (r2, -y2, z2), (r3, -y3, z3)] - - faces += [(p, p + 4, p + 5, p + 1), (p + 1, p + 5, p + 6, p + 2), (p + 2, p + 6, p + 7, p + 3)] - - ct += 1 - x += bl + spacing - z += bw - - return verts, faces - - -def vinyl_dutch_lap(oh, ow, sloped, slope, is_length_vary, length_vary, bw, faces, verts, max_boards, spacing): - z = 0 - last_x = ow - start_x = 0 - theta = atan(HI / (bw / 3)) - - if sloped: - slope = recalculate_slope(slope, oh, ow) - square = oh - ((slope * (ow / 2)) / 12) # z height where slope starts - else: - square = oh - - while z < oh: - x = start_x - ct = 1 - - if z + bw > oh: - bw = oh - z - theta = atan(HI / (bw / 3)) - - tb, ttb = bw / 3, 2 * bw / 3 - - while x < last_x: - p = len(verts) - - if is_length_vary: - v = ow * length_vary * 0.49 - bl = uniform((ow / 2) - v, (ow / 2) + v) - if x + bl + spacing > ow or ct >= max_boards: - bl = ow - x - if sloped and x < ow / 2 and bl < bw * (12 / slope): # make sure longer than slope's run - bl = bw * (12 / slope) * 1.1 - else: - bl = ow - - l1, l2, l3, l4, l5 = [x] * 5 - r1, r2, r3, r4, r5 = [x + bl] * 5 - z1, z2, z3, z4, z5 = z, z + ttb / 2, z + ttb, z + ttb + tb / 2, z + bw - y4 = (bw / 6) * tan(theta) - - if sloped and z5 > square: - if z1 < square < z3: - z2 = square - elif z3 < square < z5: - z4 = square - y4 = (z5 - z4) * tan(theta) - - if x == start_x: - if z1 < square < z3: - l1, l2, l3, l4, l5 = [x + (i - z2) * (12 / slope) for i in [z2, z2, z3, z4, z5]] - elif z3 < square < z5: - l1, l2, l3, l4, l5 = [x + (i - z4) * (12 / slope) for i in [z4, z4, z4, z4, z5]] - else: - l1, l2, l3, l4, l5 = [x + i * (12 / slope) for i in [0, ttb / 2, ttb, ttb + tb / 2, bw]] - start_x = l5 - if x + bl + spacing > last_x or (x + bl > ow / 2 and x + bl + spacing > last_x - bw * (12 / slope)) or \ - ct >= max_boards: - if z1 < square < z3: - r1, r2, r3, r4, r5 = [last_x - (i - z2) * (12 / slope) for i in [z2, z2, z3, z4, z5]] - elif z3 < square < z5: - r1, r2, r3, r4, r5 = [last_x - (i - z4) * (12 / slope) for i in [z4, z4, z4, z4, z5]] - else: - r1, r2, r3, r4, r5 = [last_x - i * (12 / slope) for i in [0, ttb / 2, ttb, ttb + tb / 2, bw]] - last_x = r5 - bl = ow - - verts += [(l1, 0, z1), (l1, -HI, z1), (l2, -HI, z2), (l3, -HI, z3), (l4, -y4, z4), (l5, 0, z5), - (r1, 0, z1), (r1, -HI, z1), (r2, -HI, z2), (r3, -HI, z3), (r4, -y4, z4), (r5, 0, z5)] - - for i in range(5): - faces += [(p, p + 1, p + 7, p + 6)] - p += 1 - - ct += 1 - x += bl + spacing - z += bw - - return verts, faces - - -def tin_base(oh, ow, sloped, slope, faces, verts, steps, x_off): # uses profile from steps to create sheets - x = 0 - ct = 0 - - while x + steps[ct][0] < ow: - if sloped: - z = slope_height(x + steps[ct][0], slope, ow, oh) + z = 0 + upper_x, upper_z = dims + while z < upper_z: + x = 0 + + cur_width = width_variance() + break_width = cur_width * break_p + y = thickness_variance() # do thickness per row + while x < upper_x: + cur_length = length_variance() + + for xx in (x, x+cur_length): + verts += [ + (xx, 0, z), + (xx, -y, z), + (xx, -y, z+break_width), + (xx, 0, z+cur_width) + ] + + p = len(verts) - 8 + faces.extend(( + (p, p+1, p+5, p+4), + (p+1, p+2, p+6, p+5), + (p+2, p+3, p+7, p+6) + )) + + x += cur_length + gap + z += cur_width + Units.ETH_INCH # shift up width + thickness + + @staticmethod + def _shiplap(dims: tuple, props, verts, faces): + length, width, th = props.board_length_long, props.board_width_medium, props.thickness + gap_length, gap_width = props.gap_lengthwise, props.gap_widthwise + + length_variance = JVSiding._create_variance_function(props.vary_length, length, props.length_variance) + width_variance = JVSiding._create_variance_function(props.vary_width, width, props.width_variance) + thickness_variance = JVSiding._create_variance_function(props.vary_thickness, th, props.thickness_variance) + + upper_x, upper_z = dims + if props.orientation == "vertical": + x = 0 + while x < upper_x: + z = 0 + dx, y = width_variance(), -thickness_variance() + while z < upper_z: + dz = length_variance() + + p = len(verts) + for zz in (z, z+dz): + verts += [ + (x, 0, zz), + (x, y, zz), + (x+dx, y, zz), + (x+dx, 0, zz), + (x+dx+gap_width, 0, zz) + ] + + # faces + for _ in range(4): + faces.append((p, p+1, p+6, p+5)) + p += 1 + + z += dz + gap_length + x += dx + gap_width else: - z = oh - - verts += [(x + steps[ct][0], -steps[ct][1], 0), (x + steps[ct][0], -steps[ct][1], z)] - - ct_pre = ct - ct = (ct + 1) % len(steps) # wrap around - - if sloped and x + steps[ct_pre][0] < ow / 2 < x + steps[ct][0]: # place middle row - slp = (steps[ct][1] - steps[ct_pre][1]) / (steps[ct][0] - steps[ct_pre][0]) # slope: (y2 - y1) / (x2 - x1) - y = -steps[ct_pre][1] - (((ow / 2) - x - steps[ct_pre][0]) * slp) - verts += [(ow / 2, y, 0), (ow / 2, y, oh)] - if ct == 0: - x += x_off - - if sloped: - z = slope_height(ow, slope, ow, oh) - else: - z = oh - - ct_pre = (ct - 1) % len(steps) - slp = (steps[ct][1] - steps[ct_pre][1]) / (steps[ct][0] - steps[ct_pre][0]) # slope: (y2 - y1) / (x2 - x1) - y = -steps[ct_pre][1] - ((ow - verts[len(verts) - 1][0]) * slp) - verts += [(ow, y, 0), (ow, y, z)] - - p = 0 - for i in range(int(len(verts) / 2) - 1): - faces += [(p, p + 1, p + 3, p + 2)] - p += 2 - - -def tin_normal(oh, ow, sloped, slope, faces, verts): - qi, ei, si = HI / 2, HI / 4, HI / 8 - tqi, fei = 0.75 / METRIC_INCH, 0.625 / METRIC_INCH - itqi, ifei = I + tqi, I + fei - - steps = [[0, 0], [HI, tqi], [fei, tqi + ei], [fei + si, I], [I + si, I], [I + ei, tqi + ei], [I + qi, tqi]] - for i in [itqi, itqi + ifei + I + qi]: - steps += [[i, 0], [i + ifei, 0], [i + ifei + qi, ei], [i + ifei + I, ei]] - steps += [[9 / METRIC_INCH - ifei, 0]] - - tin_base(oh, ow, sloped, slope, faces, verts, steps, 9 / METRIC_INCH) - - return verts, faces - - -def tin_angular(oh, ow, sloped, slope, faces, verts): - qi, ei = HI / 2, HI / 4 - iqi = I + qi - pan = 6.5 / 3 / METRIC_INCH + z = 0 + while z < upper_z: + x = 0 + dz, y = width_variance(), -thickness_variance() + while x < upper_x: + dx = length_variance() + + p = len(verts) + for xx in (x, x+dx): + verts += [ + (xx, 0, z), + (xx, y, z), + (xx, y, z+dz), + (xx, 0, z+dz), + (xx, 0, z+dz+gap_width) + ] + + # faces + for _ in range(4): + faces.append((p, p+1, p+6, p+5)) + p += 1 + + x += dx + gap_length + z += dz + gap_width + + @staticmethod + def _clapboard(dims: tuple, props, verts, faces): + length, width, gap = props.board_length_long, props.board_width_medium, props.gap_uniform + th = props.thickness_thin + length_variance = JVSiding._create_variance_function(props.vary_length, length, props.length_variance) + width_variance = JVSiding._create_variance_function(props.vary_width, width, props.width_variance) - steps = [[0, 0], [HI, iqi], [I + HI, iqi]] - for i in [2 * I, 2 * I + pan + HI + iqi]: - steps += [[i, 0], [i + pan, 0], [i + pan + qi, ei], [i + pan + qi + iqi, ei]] - steps += [[1 / METRIC_FOOT - pan, 0]] - - tin_base(oh, ow, sloped, slope, faces, verts, steps, 1 / METRIC_FOOT) - - return verts, faces - - -def tin_screws(oh, ow, sloped, slope): - verts, faces = [], [] - m_i = METRIC_INCH # metric_inch - rows = int((oh - 4 / m_i) / (16 / m_i)) - - row_offset = (oh - 4 / m_i) / rows - column_offset = 9 / m_i - dia_w = 0.25 / m_i - dia_s = 0.15 / m_i - - x_off = 0.05715 - cur_z = 2 / m_i - - while cur_z < oh: - cur_x = x_off - - while cur_x < ow: - exists = True - - if sloped: - # calculate height at current x value - if (cur_x < (ow / 2) and cur_z > ((slope / 12) * (cur_x - (ow / 2))) + oh) or \ - cur_z > ((-slope / 12) * (cur_x - (ow / 2))) + oh: - exists = False - - if exists: - p = len(verts) - # step by a sixteenth of an inch each time - for i in range(2): - for j in range(-30, 330, 60): - x, z = point_rotation((cur_x + dia_w, cur_z), (cur_x, cur_z), radians(j)) - verts += [(x, i * -((1 / 16) / m_i), z)] - for i in range(2): - for j in range(-30, 330, 60): - x, z = point_rotation((cur_x + dia_s, cur_z), (cur_x, cur_z), radians(j)) - verts += [(x, -((1 / 16) / m_i) + (i * -(0.2 / m_i)), z)] - - # faces - for i in range(3): - tp = p - for j in range(5): - faces += [(tp, tp + 1, tp + 7, tp + 6)] - tp += 1 - faces += [(tp, tp - 5, tp + 1, tp + 6)] - p += 6 - faces += [(p, p + 1, p + 2, p + 5), (p + 5, p + 2, p + 3, p + 4)] - - cur_x += column_offset - cur_z += row_offset - - return verts, faces - - -def bricks(oh, ow, b_w, b_h, b_offset, gap, ran_offset, b_vary, faces, verts, corners, inverted, - left, right): - z, th = 0, 3.5 / METRIC_INCH - b_offset, b_vary = b_offset / 100, b_vary / 100 - stepped = inverted - cur_off = inverted - - while z < oh: - x = -th - gap if corners and stepped and left else 0 - end_x = ow + th + gap if corners and not stepped and right else ow - - if z + b_h > oh: - b_h = oh - z - - while x < end_x: - w = b_w - p = len(verts) - - if x == -th - gap: - w = th + gap + b_w / 2 - gap / 2 - elif cur_off and x == 0 and (not ran_offset or (corners and not left)): - w = b_w * b_offset - gap / 2 - elif cur_off and x == 0 and ran_offset: - v = (b_w / 2) * b_vary - w = uniform((b_w / 2) - v, (b_w / 2) + v) - gap / 2 - - if x + b_w > end_x: - w = end_x - x - - verts += [(x, 0, z), (x, 0, z + b_h), (x, -th, z), (x, -th, z + b_h), (x + w, -th, z), - (x + w, -th, z + b_h), (x + w, 0, z), (x + w, 0, z + b_h)] - faces += [(p, p + 2, p + 3, p + 1), (p + 2, p + 4, p + 5, p + 3), (p + 4, p + 6, p + 7, p + 5), - (p, p + 1, p + 7, p + 6), (p + 1, p + 3, p + 5, p + 7), (p, p + 6, p + 4, p + 2)] - - x += w + gap - stepped = not stepped - cur_off = not cur_off - z += b_h + gap - - return verts, faces - - -def slope_cutter(oh, ow, slope, corners, b_w): # creates object to cut slope - verts, faces = [], [] - if not corners: - slope = recalculate_slope(slope, oh, ow) - edge = slope_height(-0.1, slope, ow, oh) - - verts += [(-0.1, 0.5, edge), (-0.1, -0.5, edge), (ow / 2, 0.5, oh), (ow / 2, -0.5, oh), (ow + 0.1, 0.5, edge), - (ow + 0.1, -0.5, edge), (-0.1, 0.5, oh + 0.5), (-0.1, -0.5, oh + 0.5), (ow / 2, 0.5, oh + 0.5), - (ow / 2, -0.5, oh + 0.5), (ow + 0.1, 0.5, oh + 0.5), (ow + 0.1, -0.5, oh + 0.5)] - else: - square = oh - ((slope * ((ow + (2 * b_w)) / 2)) / 12) - if square <= 0: - slope = ((24 * oh) / (ow + (2 * b_w))) - 0.01 - edge = slope_height(-b_w - 0.1, slope, ow, oh) - - verts += [(-b_w - 0.1, 0.5, edge), (-b_w - 0.1, -0.5, edge), (ow / 2, 0.5, oh), (ow / 2, -0.5, oh), - (ow + b_w + 0.1, 0.5, edge), (ow + b_w + 0.1, -0.5, edge), (-b_w - 0.1, 0.5, oh + 1), - (-b_w - 0.1, -0.5, oh + 1), (ow / 2, 0.5, oh + 1), (ow / 2, -0.5, oh + 1), - (ow + b_w + 0.1, 0.5, oh + 1), (ow + b_w + 0.1, -0.5, oh + 1)] - - faces += [(0, 2, 3, 1), (2, 4, 5, 3), (0, 1, 7, 6), (1, 3, 9, 7), (3, 5, 11, 9), (5, 4, 10, 11), (4, 2, 8, 10), - (2, 0, 6, 8), (6, 7, 9, 8), (8, 9, 11, 10)] - - return verts, faces - - -def mortar(oh, ow, m_d, sloped, slope, corners, left, right, convert, bw, gap, brick, x_off): - verts, faces = [], [] - depth = 3.5 / METRIC_INCH - y = depth - m_d if brick else m_d - - # expand mortar if brick has usable corners or if it is a converted object - x, fx, th = 0, ow, 3.5 / METRIC_INCH - - if brick and convert == "convert": - fx = ow + (bw / 2) - if brick and corners and left: - x = -th - gap + m_d - if brick and corners and right: - fx = ow + gap + th - m_d - - if sloped: - slope = recalculate_slope(slope, oh, ow) - square = oh - ((slope * (ow / 2)) / 12) # z height where slope starts - ls, rs = square, square - - # get correct square height if usable corners is enabled - if corners and left: - ls = oh - ((slope * ((ow / 2) + ((bw / 2) - gap))) / 12) - elif corners and right: - rs = oh - ((slope * ((ow / 2) + ((bw / 2) - gap))) / 12) - - x += x_off - if sloped: - verts += [(x, 0.0, 0.0), (x, -y, 0.0), (fx, -y, 0.0), (fx, 0.0, 0.0), (x, 0.0, ls), (x, -y, ls), (fx, -y, rs), - (fx, 0.0, rs), (ow / 2, 0.0, oh), (ow / 2, -y, oh)] - faces += [(0, 3, 2, 1), (0, 1, 5, 4), (1, 2, 6, 5), (2, 3, 7, 6), (0, 4, 7, 3), (4, 5, 9, 8), (5, 6, 9), - (6, 7, 8, 9), (7, 4, 8)] - else: - verts += [(x, 0.0, 0.0), (x, -y, 0.0), (fx, -y, 0.0), (fx, 0.0, 0.0), (x, 0.0, oh), (x, -y, oh), (fx, -y, oh), - (fx, 0.0, oh)] - faces += [(0, 3, 2, 1), (0, 1, 5, 4), (1, 2, 6, 5), (2, 3, 7, 6), (0, 4, 7, 3), (4, 5, 6, 7)] - - return verts, faces - - -def solider_bricks(corner_data, b_h, gap, b_w, oh): - verts, faces = [], [] - d = 3.75 / METRIC_INCH - # corner data: x, z, far x, far z - for i in corner_data: - x, z = i[0] + gap, i[3] - bw2 = oh - z if z + b_w > oh else b_w - - if z < oh: - while x < i[2]: - bh2 = b_h - p = len(verts) - - if x + b_h + gap > i[2]: - bh2 = i[2] - x - gap - - verts += [(x, 0.0, z), (x, -d, z), (x + bh2, -d, z), (x + bh2, 0.0, z), (x, 0.0, z + bw2), - (x, -d, z + bw2), (x + bh2, -d, z + bw2), (x + bh2, 0.0, z + bw2)] - x += gap + bh2 - faces += [(p, p + 3, p + 2, p + 1), (p, p + 1, p + 5, p + 4), (p + 1, p + 2, p + 6, p + 5), - (p + 2, p + 3, p + 7, p + 6), (p, p + 4, p + 7, p + 3), (p + 4, p + 5, p + 6, p + 7)] - - return verts, faces - - -# TODO: Try and speed up stone generation -def stone_sizes(num, rows, columns, grid): - block, out = [], [] - row, col = int(num / columns), num % columns + z = 0 + upper_x, upper_z = dims + while z < upper_z: + x = 0 + + vertical_width = sqrt(width_variance()**2 - th**2) + while x < upper_x: + length = length_variance() + + trimmed_length = min(length, upper_x-x) + trimmed_width = min(vertical_width, upper_z-z) + + verts += [ + (x, -th, z), + (x+trimmed_length, -th, z), + (x+trimmed_length, 0, z+trimmed_width), + (x, 0, z+trimmed_width) + ] + + p = len(verts) - 4 + faces.append((p, p+3, p+2, p+1)) + + x += length + gap + z += vertical_width + + @staticmethod + def _tin_regular(dims: tuple, props, verts, faces): + ridge_steps = ( + (0, 0), + (Units.H_INCH, Units.TQ_INCH), + (5*Units.ETH_INCH, 7*Units.ETH_INCH), + (11*Units.STH_INCH, Units.INCH), + (17*Units.STH_INCH, Units.INCH), + (9*Units.ETH_INCH, 7*Units.ETH_INCH), + (5*Units.Q_INCH, 3*Units.Q_INCH) + ) + + valley_steps = ( + (0, 0), + (13*Units.ETH_INCH, 0), + (15*Units.ETH_INCH, Units.ETH_INCH), + (21*Units.ETH_INCH, Units.ETH_INCH) + ) + + upper_x = dims[0] + offset_between_valley_accents = 23*Units.ETH_INCH + for z in (0, dims[1]): + x = 0 + while x < upper_x+offset_between_valley_accents: + for step in ridge_steps: + verts.append((x+step[0], -step[1], z)) + x += 7*Units.Q_INCH + + for _ in range(2): + for step in valley_steps: + verts.append((x+step[0], -step[1], z)) + x += offset_between_valley_accents + + verts.append((x, 0, z)) # finish valley ridge + x += offset_between_valley_accents-Units.INCH + + # faces + offset = len(verts) // 2 + for i in range(offset-1): + faces.append((i, i+1, i+offset+1, i+offset)) + + @staticmethod + def _tin_angular(dims: tuple, props, verts, faces): + pan = 3*Units.INCH + ridge_steps = ((0, 0), (Units.H_INCH, 5*Units.Q_INCH), (3*Units.H_INCH, 5*Units.Q_INCH), (2*Units.INCH, 0)) + valley_steps = ((0, 0), (pan, 0), (pan + Units.Q_INCH, Units.ETH_INCH), (pan + 3*Units.H_INCH, Units.ETH_INCH)) + + upper_x = dims[0] + for z in (0, dims[1]): + x = 0 + while x < upper_x+pan: + for step in ridge_steps: + verts.append((x+step[0], -step[1], z)) + x += 2 * Units.INCH + + for _ in range(2): + for step in valley_steps: + verts.append((x+step[0], -step[1], z)) + x += pan + 7*Units.Q_INCH + + verts.append((x, 0, z)) + x += pan + + # faces + offset = len(verts) // 2 + for i in range(offset - 1): + faces.append((i, i + 1, i + offset + 1, i + offset)) + + @staticmethod + def _brick(dims: tuple, props, verts, faces): + length, height, gap, th = props.brick_length, props.brick_height, props.gap_uniform, props.thickness_thick + + first_length_for_fixed_offset = length * (props.row_offset / 100) + if first_length_for_fixed_offset == 0: + first_length_for_fixed_offset = length + + offset_length_variance = JVSiding._create_variance_function(props.vary_row_offset, length / 2, + props.row_offset_variance) - for r in range(row - 3, row + 4, 1): - block_row = [] - for c in range(col - 3, col + 4, 1): - pos = r * columns + c - if 0 <= r < rows and 0 <= c < columns and not isinstance(grid[pos], bool): - block_row.append(True) - else: - block_row.append(False) - block.append(block_row) - - perms = list(permutations([1, 2, 3, 4], 2)) - perms += [(2, 2), (3, 3), (4, 4)] - - for per in perms: - # check if all positions are valid - for start_r in range(4 - per[0], 4, 1): - for start_c in range(4 - per[1], 4, 1): - safe = True - pos = [] - for r in range(0, per[0], 1): - for c in range(0, per[1], 1): - pos.append((row - 3 + start_r + r) * columns + (col - 3 + start_c + c)) - if not block[start_r + r][start_c + c]: - safe = False - - if safe: - out.append({"indices": pos, "height": per[0], "width": per[1]}) - - return out - - -def stone_grid(oh, ow, avw, avh, sr): # generate grid and figure stone sizes - hh, hw = avh / 2, avw / 2 - rows, columns = int(oh / hh), int(ow / hw) - hh2, hw2 = oh / rows, ow / columns - grid = [i for i in range(rows * columns)] - out = [] - clist = grid[:] # list to have numbers chosen from - - while clist: - # pick number choice - num = choice(clist) if sr else clist[0] - - # calculate possible sizes for this number - possible = stone_sizes(num, rows, columns, grid) - sizes = {i: [] for i in ["2", "3", "4", "6", "8", "9", "12", "16"]} - for i in possible: - if str(len(i["indices"])) in sizes: - sizes[str(len(i["indices"]))].append(i) - - # probability - if not sr: - rock = {"indices": [num, num + 1, num + columns, num + columns + 1], "width": 2, "height": 2} - else: - lines = {"2": 2, "3": 2, "4": 10, "6": 4, "8": 5, "9": 6, "12": 7, "16": 8} - pick = [] - for i in sizes.keys(): - if sizes[i]: - c = [pick.append(i) for i2 in range(int(lines[i] / 100 * sr + (10 - lines[i])))] - c = [pick.append("1") for i2 in range(int(0.02 * sr + 8))] - - size = choice(pick) - if size != "1": - rock = choice(sizes[size]) + z = 0 + odd = False + upper_x, upper_z = dims + while z < upper_z: + if odd and props.joint_left: + x = -th - gap else: - rock = {"indices": [num], "width": 1, "height": 1} - - out.append(rock) - for i in rock["indices"]: - clist.remove(i) - grid[i] = False + x = 0 + + trimmed_height = min(height, upper_z-z) + while x < upper_x: + cur_length = length + if x == 0: + if odd and not props.vary_row_offset: + cur_length = first_length_for_fixed_offset + elif props.vary_row_offset: + cur_length = offset_length_variance() + + if not odd and props.joint_right: + trimmed_length = min(cur_length, upper_x + th + gap - x) + else: + trimmed_length = min(cur_length, upper_x - x) + + verts += [ + (x, 0, z), + (x+trimmed_length, 0, z), + (x+trimmed_length, 0, z+trimmed_height), + (x, 0, z+trimmed_height) + ] + + p = len(verts) - 4 + faces.append((p, p+3, p+2, p+1)) + + x += cur_length + gap + z += height + gap + odd = not odd + + @staticmethod + def _shakes(dims: tuple, props, verts, faces): + length, width, gap = props.shake_length, props.shake_width, props.gap_uniform + th_y, hl = -2 * props.thickness_thin, length / 2 + + first_width_for_fixed_offset = width * (props.row_offset / 100) + if first_width_for_fixed_offset == 0: + first_width_for_fixed_offset = width + + offset_width_variance = JVSiding._create_variance_function(props.vary_row_offset, width / 2, + props.row_offset_variance) + width_variance = JVSiding._create_variance_function(props.vary_width, width, props.width_variance) + upper_x, upper_z = dims + + # bottom row backing layer + verts += [ + (0, th_y / 2, 0), + (upper_x, th_y / 2, 0), + (upper_x, 0, hl), + (0, 0, hl) + ] + faces.append((0, 3, 2, 1)) - return out, columns, hh2, hw2 + z = 0 + odd = False + while z < upper_z: + x = 0 + + while x < upper_x: + cur_width = width_variance() + if x == 0: + if odd and not props.vary_row_offset: + cur_width = first_width_for_fixed_offset + elif props.vary_row_offset: + cur_width = offset_width_variance() + + dx = min(cur_width, upper_x-x) + dz = min(length, upper_z-z) + + verts += [ + (x, th_y, z), + (x+dx, th_y, z), + (x+dx, 0, z+dz), + (x, 0, z+dz) + ] + + p = len(verts) - 4 + faces.append((p, p+3, p+2, p+1)) + + x += cur_width + gap + z += hl + odd = not odd + + @staticmethod + def _scallop_shakes(dims: tuple, props, verts, faces): + length, width, gap, th = props.shake_length, props.shake_width, props.gap_uniform, props.thickness_thin + rad, res = width / 2, props.scallop_resolution + rest_z, ang_step = length - rad, radians(180 / (res+1)) + th_y, y_slope = -th, -th / rest_z + + first_width_for_fixed_offset = width * (props.row_offset / 100) + if first_width_for_fixed_offset == width: + first_width_for_fixed_offset = 0 + + offset_width_variance = JVSiding._create_variance_function(props.vary_row_offset, width / 2, + props.row_offset_variance) + + upper_x, upper_z = dims + odd = False + z = rad + while z < upper_z: + x = 0 + if odd and not props.vary_row_offset: + x = -first_width_for_fixed_offset + elif props.vary_row_offset: + x = -offset_width_variance() + + while x < upper_x: + cx = x + rad + top = z+rest_z + p = len(verts) + for i in range(res+2): + ang = ang_step * i + dx = rad * cos(ang) + dz = rad * sin(ang) + verts += [(cx-dx, th_y + (y_slope*dz), z-dz), (cx-dx, 0, top)] -def stone_faces(oh, ow, avw, avh, sr, br, b_gap): # convert grid data to faces - verts, faces = [], [] - g, depth = b_gap / 2, 0.0508 - stones, columns, hh, hw = stone_grid(oh, ow, avw, avh, sr) - h, w = hh - (2 * g), hw - (2 * g) + # faces + for i in range(res+1): + faces.append((p, p+1, p+3, p+2)) + p += 2 - for rock in stones: - if br: - vary = depth * br * 0.0045 - y = uniform(depth - vary, depth + vary) - else: - y = depth - - p = len(verts) - r, c = int(rock["indices"][0] / columns), rock["indices"][0] % columns - x, z = (c * hw) + g, (hh * r) + g - x2 = x + w * rock["width"] + b_gap * (rock["width"] - 1) - z2 = z + h * rock["height"] + b_gap * (rock["height"] - 1) - - verts += [(x, 0, z), (x, -y, z), (x2, 0, z), (x2, -y, z), (x2, 0, z2), (x2, -y, z2), (x, 0, z2), (x, -y, z2)] - faces += [(p, p + 1, p + 7, p + 6), (p + 1, p + 3, p + 5, p + 7), (p + 3, p + 2, p + 4, p + 5), - (p, p + 6, p + 4, p + 2), (p + 6, p + 7, p + 5, p + 4), (p, p + 2, p + 3, p + 1)] - - return verts, faces - - -def update_siding(self, context): - o = context.object - - mats = [] - for i in o.data.materials: - mats.append(i.name) - - pre_scale = tuple(o.scale.copy()) - if tuple(o.scale) != (1.0, 1.0, 1.0) and o.jv_pre_jv_dims != "none": # apply scale - bpy.ops.object.transform_apply(scale=True) - - # update jv_pre_jv_dims - if o.jv_dims == "none": - dim = object_dimensions(o) - o.jv_dims = str(dim[0]) + ", " + str(dim[1]) - else: - dim_temp = o.jv_dims.split(",") - dim = [float(dim_temp[0]), float(dim_temp[1])] - - # create object - if o.jv_object_add == "add": - verts, faces, corner_data, corner_data_l, v, f = create_siding(context, o.jv_siding_types, - o.jv_tin_siding_types, o.jv_wood_siding_types, - o.jv_vinyl_siding_types, o.jv_is_slope, - o.jv_over_width, o.jv_over_height, o.jv_b_width, - o.jv_slope, o.jv_is_width_vary, o.jv_width_vary, - o.jv_is_cutout, o.jv_batten_width, o.jv_spacing, - o.jv_is_length_vary, o.jv_length_vary, - o.jv_max_boards, o.jv_br_width, o.jv_br_height, - o.jv_br_offset, o.jv_br_gap, o.jv_br_ran_offset, - o.jv_br_vary, o.jv_is_corner, o.jv_is_invert, - o.jv_is_soldier, o.jv_is_left, o.jv_is_right, - o.jv_av_width, o.jv_av_height, o.jv_random_size, - o.jv_random_bump, o.jv_x_offset) - elif o.jv_object_add == "convert": - ow, oh = dim[0], dim[1] - o.jv_pre_jv_dims = "something" - - verts, faces, corner_data, corner_data_l, v, f = create_siding(context, o.jv_siding_types, - o.jv_tin_siding_types, o.jv_wood_siding_types, - o.jv_vinyl_siding_types, o.jv_is_slope, ow, oh, - o.jv_b_width, o.jv_slope, o.jv_is_width_vary, - o.jv_width_vary, o.jv_is_cutout, - o.jv_batten_width, o.jv_spacing, - o.jv_is_length_vary, o.jv_length_vary, - o.jv_max_boards, o.jv_br_width, - o.jv_br_height, o.jv_br_offset, o.jv_br_gap, - o.jv_br_ran_offset, o.jv_br_vary, o.jv_is_corner, - o.jv_is_invert, o.jv_is_soldier, o.jv_is_left, - o.jv_is_right, o.jv_av_width, o.jv_av_height, - o.jv_random_size, o.jv_random_bump, - o.jv_x_offset) - else: - return - old_mesh = o.data - - if o.jv_object_add == "add": - mesh = bpy.data.meshes.new(name="siding") - mesh.from_pydata(verts, [], faces) - mesh.update(calc_edges=True) - elif o.jv_object_add == "convert": - mesh = bpy.data.meshes.new(name="siding") - mesh.from_pydata(verts, [], faces) - mesh.update(calc_edges=True) - - if o.jv_cut_name == "none": # if cutter object hasn't been created yet - for ob in context.selected_objects: - ob.select = False - - cutter = bpy.data.objects.new(o.name + "_cutter", o.data.copy()) - context.scene.objects.link(cutter) - cutter.location = o.location - cutter.rotation_euler = o.rotation_euler - cutter.scale = o.scale - o.jv_cut_name = cutter.name - - cutter.select = True - bpy.context.scene.objects.active = cutter - bpy.ops.object.modifier_add(type="SOLIDIFY") - pos = len(cutter.modifiers) - 1 - bpy.context.object.modifiers[pos].offset = 0 - bpy.context.object.modifiers[pos].thickness = 1 - - o.select = True - bpy.context.scene.objects.active = o - - # find object rotation - rot = list(rot_from_normal(o.data.polygons[0].normal)) - rot[2] = round(rot[2] - radians(270), 4) - rot[1] = round(rot[1] - radians(90), 4) - - # rotation and vertices to find corner - x, y, z = None, None, None - for i in [vert.co for vert in o.data.vertices]: # find smallest x, y, and z positions - if x is None: - x = i[0] - y = i[1] - # smallest x - elif (0 <= rot[2] < round_rad(90) or 0 >= rot[2] > round_rad(-90)) and i[0] < x: - x = i[0] - y = i[1] - # largest x - elif (round_rad(90) < rot[2] <= round_rad(180) or round_rad(-90) > rot[2] >= round_rad(-180)) and \ - i[0] > x: - x = i[0] - y = i[1] - # at 90 degrees, then all x the same so pick smallest y - elif rot[2] == round_rad(90) and i[1] < y: - x = i[0] - y = i[1] - # at -90 degrees, then all x the same so pick largest y - elif rot[2] == round_rad(-90) and i[1] > y: - x = i[0] - y = i[1] - - if z is None: - z = i[2] - elif i[2] < z: - z = i[2] - - position = o.matrix_world * Vector((x, y, z)) # get world space - coords = [tuple(position), (rot[1], 0, rot[2])] # rearrange order of rot - o.jv_previous_rotation = str(rot) - elif o.jv_cut_name in bpy.data.objects: - bpy.data.objects[o.jv_cut_name].name = o.name + "_cutter" - o.jv_cut_name = o.name + "_cutter" - coords = [o.location, o.rotation_euler] - else: - self.report({"ERROR"}, "JARCH Vis: Can't Find Cutter Object") - - for i in bpy.data.objects: - if i.data == old_mesh: - i.data = mesh - old_mesh.user_clear() - bpy.data.meshes.remove(old_mesh) - - # create battens if needed - if v: - batten_mesh = bpy.data.meshes.new(o.name + "_battens") - batten_mesh.from_pydata(v, [], f) - battens = bpy.data.objects.new(batten_mesh.name, batten_mesh) - context.scene.objects.link(battens) - battens.location = o.location - battens.rotation_euler = o.rotation_euler - battens.scale = o.scale - - bpy.context.scene.objects.active = o - # create soldiers if needed - if o.jv_is_soldier and o.jv_is_cutout and o.jv_siding_types == "5": - verts2, faces2 = solider_bricks(corner_data, o.jv_br_height, o.jv_br_gap, o.jv_br_width, o.jv_over_height) - p_mesh = bpy.data.meshes.new("soldier") - p_mesh.from_pydata(verts2, [], faces2) - soldier = bpy.data.objects.new("soldier", p_mesh) - context.scene.objects.link(soldier) - soldier.location = o.location - soldier.rotation_euler = o.rotation_euler - soldier.scale = o.scale - - if o.jv_is_bevel: - context.scene.objects.active = soldier - bpy.ops.object.modifier_add(type="BEVEL") - soldier.modifiers["Bevel"].width = 0.0024384 - soldier.modifiers["Bevel"].use_clamp_overlap = False - soldier.modifiers["Bevel"].segments = o.jv_bevel_res - bpy.ops.object.modifier_apply(apply_as="DATA", modifier="Bevel") - context.scene.objects.active = o - - # solidify and bevel as needed - if o.jv_siding_types == "2": # vinyl - bpy.ops.object.modifier_add(type="BEVEL") - pos = len(o.modifiers) - 1 - bpy.context.object.modifiers[pos].width = 0.003048 - bpy.context.object.modifiers[pos].use_clamp_overlap = o.jv_vinyl_siding_types != "3" - bpy.context.object.modifiers[pos].segments = o.jv_bevel_res - bpy.context.object.modifiers[pos].limit_method = "ANGLE" - bpy.context.object.modifiers[pos].angle_limit = 1.4 - bpy.ops.object.modifier_apply(apply_as="DATA", modifier=o.modifiers[pos].name) - bpy.ops.object.modifier_add(type="SOLIDIFY") - bpy.context.object.modifiers[pos].thickness = 0.002 - bpy.ops.object.modifier_apply(apply_as="DATA", modifier=o.modifiers[pos].name) - elif o.jv_siding_types == "3": # tin - bpy.ops.object.modifier_add(type="SOLIDIFY") - pos = len(o.modifiers) - 1 - bpy.context.object.modifiers[pos].thickness = 0.0003429 - bpy.ops.object.modifier_apply(apply_as="DATA", modifier=o.modifiers[pos].name) - # brick or stone - elif (o.jv_siding_types in ("5", "6") or (o.jv_siding_types == "1" and o.jv_wood_siding_types == "1")) and \ - o.jv_is_bevel: - bpy.ops.object.modifier_add(type="BEVEL") - pos = len(o.modifiers) - 1 - width = o.jv_bevel_width if o.jv_siding_types == "1" else 0.0024384 - bpy.context.object.modifiers[pos].width = width - bpy.context.object.modifiers[pos].use_clamp_overlap = False - bpy.context.object.modifiers[pos].segments = o.jv_bevel_res - bpy.ops.object.modifier_apply(apply_as="DATA", modifier=o.modifiers[pos].name) - - # cut slope on brick and stone - if o.jv_siding_types in ("5", "6") and o.jv_object_add == "add" and o.jv_is_slope: - verts2, faces2 = slope_cutter(o.jv_over_height, o.jv_over_width, o.jv_slope, o.is_corner, o.jv_br_width) - bc_s = bpy.data.meshes.new(o.name + "_slope_cut") - bc_s.from_pydata(verts2, [], faces2) - cut = bpy.data.objects.new(o.name + "_slope_cut", bc_s) - context.scene.objects.link(cut) - cut.location = o.location - cut.rotation_euler = o.rotation_euler - cut.scale = o.scale - - ob_to_cut = [o] - if o.jv_is_soldier and o.jv_is_cutout and o.jv_siding_types == "5": # cut soldiers - ob_to_cut.append(soldier) - - for ob in ob_to_cut: - bpy.context.scene.objects.active = ob - bpy.ops.object.modifier_add(type="BOOLEAN") - pos = len(o.modifiers) - 1 - ob.modifiers[pos].object = cut - ob.modifiers[pos].solver = "CARVE" - ob.modifiers[pos].operation = "DIFFERENCE" - bpy.ops.object.modifier_apply(apply_as="DATA", modifier=ob.modifiers[pos].name) - - o.select, cut.select = False, True - bpy.ops.object.delete() - o.select = True - - if o.jv_siding_types in ("5", "6"): # mortar - brick_stone = o.jv_siding_types == "5" - depth = o.jv_br_m_depth if o.jv_siding_types == "5" else o.jv_st_m_depth - - if o.jv_object_add == "convert": - verts3, faces3 = mortar(dim[1], dim[0], depth, o.jv_is_slope, o.jv_slope, o.jv_is_corner, o.jv_is_left, - o.jv_is_right, o.jv_object_add, o.jv_br_width, o.jv_br_gap, brick_stone, - o.jv_x_offset) - else: - verts3, faces3 = mortar(o.jv_over_height, o.jv_over_width, depth, o.jv_is_slope, o.jv_slope, - o.jv_is_corner, o.jv_is_left, o.jv_is_right, o.jv_object_add, o.jv_br_width, - o.jv_br_gap, brick_stone, o.jv_x_offset) - - bc_m = bpy.data.meshes.new(o.name + "_mortar") - bc_m.from_pydata(verts3, [], faces3) - mortar_ob = bpy.data.objects.new(o.name + "_mortar", bc_m) - context.scene.objects.link(mortar_ob) - mortar_ob.location = o.location - mortar_ob.rotation_euler = o.rotation_euler - mortar_ob.scale = o.scale - - if len(mats) < 2: - mat = bpy.data.materials.new("mortar_temp") - mat.use_nodes = True - mortar_ob.data.materials.append(mat) - else: - mat = bpy.data.materials.get(mats[1]) - mortar_ob.data.materials.append(mat) - - # bevel edges slightly so that a subdivision modifier won't mess it up - o.select = False - mortar_ob.select = True - context.scene.objects.active = mortar_ob - bpy.ops.object.modifier_add(type="BEVEL") - mortar_ob.modifiers["Bevel"].width = 0.001524 - bpy.ops.object.modifier_apply(apply_as="DATA", modifier="Bevel") - o.select = True - mortar_ob.select = False - context.scene.objects.active = o - - # TODO: Tin screws not always joined - if o.jv_siding_types == "3" and o.jv_is_screws: # tin screws - if o.jv_object_add == "convert": - verts2, faces2 = tin_screws(dim[1], dim[0], o.jv_is_slope, o.jv_slope) - else: - verts2, faces2 = tin_screws(o.jv_over_height, o.jv_over_width, o.jv_is_slope, o.jv_slope) - - screws = bpy.data.meshes.new(o.name + "_screws") - screws.from_pydata(verts2, [], faces2) - screw_ob = bpy.data.objects.new(o.name + "_screws", screws) - context.scene.objects.link(screw_ob) - screw_ob.location = o.location - screw_ob.rotation_euler = o.rotation_euler - screw_ob.select = True - o.select = True - bpy.context.scene.objects.active = o - bpy.ops.object.join() - - if o.jv_is_cutout and o.jv_object_add == "add": # cutouts - bool_stuff = boolean_object(corner_data) - - if o.jv_siding_types == "5" and o.jv_is_soldier: - verts3, faces3 = boolean_object(corner_data_l) - bool_me_l = bpy.data.meshes.new(o.name + "_bool2") - bool_me_l.from_pydata(verts3, [], faces3) - bool_ob_l = bpy.data.objects.new(o.name + "_bool2", bool_me_l) - context.scene.objects.link(bool_ob_l) - bool_ob_l.location = o.location - bool_ob_l.rotation_euler = o.rotation_euler - bool_ob_l.scale = o.scale - - if bool_stuff[0]: - verts2, faces2 = bool_stuff[0], bool_stuff[1] - bool_me = bpy.data.meshes.new(o.name + "_bool") - bool_me.from_pydata(verts2, [], faces2) - bool_ob = bpy.data.objects.new(o.name + "_bool", bool_me) - context.scene.objects.link(bool_ob) - bool_ob.location = o.location - bool_ob.rotation_euler = o.rotation_euler - bool_ob.scale = o.scale - bpy.context.scene.objects.active = o - - bpy.ops.object.modifier_add(type="BOOLEAN") - pos = len(o.modifiers) - 1 - if o.jv_siding_types == "5" and o.jv_is_soldier: - bpy.context.object.modifiers[pos].object = bool_ob_l - else: - bpy.context.object.modifiers[pos].object = bool_ob - bpy.context.object.modifiers[pos].operation = "DIFFERENCE" - bpy.ops.object.modifier_apply(apply_as="DATA", modifier=o.modifiers[pos].name) - - for ob in context.selected_objects: - ob.select = False - - if v: # battens - battens.select = True - context.scene.objects.active = battens - bpy.ops.object.modifier_add(type="BOOLEAN") - bpy.context.object.modifiers["Boolean"].object = bool_ob - bpy.context.object.modifiers["Boolean"].operation = "DIFFERENCE" - bpy.ops.object.modifier_apply(apply_as="DATA", modifier="Boolean") - battens.select = False - if o.jv_siding_types in ("5", "6"): - bpy.context.scene.objects.active = mortar_ob - bpy.ops.object.modifier_add(type="BOOLEAN") - pos = len(mortar_ob.modifiers) - 1 - bpy.context.object.modifiers[pos].object = bool_ob - bpy.context.object.modifiers[pos].operation = "DIFFERENCE" - bpy.ops.object.modifier_apply(apply_as="DATA", modifier=mortar_ob.modifiers[pos].name) - - if o.name + "_bool2" in bpy.data.objects: - bool_ob_l.select = True - - bool_ob.select = True - bpy.ops.object.delete() - o.select = True - bpy.context.scene.objects.active = o - - if o.jv_object_add == "convert": # set position - o.location = coords[0] - o.rotation_euler = coords[1] - cutter = bpy.data.objects[o.jv_cut_name] # update cutter object scale, rotation, location, origin point - - for ob in context.selected_objects: - ob.select = False - - cursor = context.scene.cursor_location.copy() - o.select = True - bpy.context.scene.objects.active = o - bpy.ops.view3d.snap_cursor_to_selected() - o.select = False - - if o.jv_is_cut == "none": - cutter.select = True - bpy.context.scene.objects.active = cutter - bpy.ops.object.origin_set(type="ORIGIN_CURSOR") - bpy.ops.object.move_to_layer(layers=[i >= 19 for i in range(20)]) - cutter.select = False - o.jv_is_cut = "cut" - - pre_rot = literal_eval(o.jv_previous_rotation) - o.select = True - bpy.context.scene.objects.active = o - cutter.location = o.location - cutter.rotation_euler = (coords[1][0] - pre_rot[1], 0, coords[1][2] - pre_rot[2]) - bpy.context.scene.cursor_location = cursor # change cursor location back to original - - if pre_scale != (1.0, 1.0, 1.0): - pre_layers = [i for i in bpy.context.scene.layers] - layers = [i >= 19 for i in range(20)] - context.scene.layers = layers - cutter.select, o.select = True, False - bpy.context.scene.objects.active = cutter - cutter.scale = (pre_scale[0], pre_scale[2], pre_scale[1]) - bpy.ops.object.transform_apply(scale=True) - cutter.select = False - context.scene.layers = pre_layers - bpy.context.scene.objects.active = o - o.select = True - - # cut objects - bpy.ops.object.modifier_add(type="BOOLEAN") - pos = len(o.modifiers) - 1 - context.object.modifiers[pos].object = bpy.data.objects[o.jv_cut_name] - context.object.modifiers[pos].solver = "CARVE" - bpy.ops.object.modifier_apply(apply_as="DATA", modifier=o.modifiers[0].name) - - if o.jv_siding_types in ("5", "6"): - o.select, mortar_ob.select = False, True - bpy.context.scene.objects.active = mortar_ob - bpy.ops.object.modifier_add(type="BOOLEAN") - pos = len(mortar_ob.modifiers) - 1 - context.object.modifiers[pos].solver = "CARVE" - bpy.context.object.modifiers[pos].object = bpy.data.objects[o.jv_cut_name] - bpy.ops.object.modifier_apply(apply_as="DATA", modifier=mortar_ob.modifiers[pos].name) - o.select, mortar_ob.select = True, False - bpy.context.scene.objects.active = o - elif v: - o.select, battens.select = False, True - bpy.context.scene.objects.active = battens - bpy.ops.object.modifier_add(type="BOOLEAN") - context.object.modifiers[pos].solver = "CARVE" - bpy.context.object.modifiers["Boolean"].object = bpy.data.objects[o.jv_cut_name] - bpy.ops.object.modifier_apply(apply_as="DATA", modifier="Boolean") - o.select, battens.select = True, False - bpy.context.scene.objects.active = o - - if o.jv_siding_types in ("5", "6"): # join mortar and brick - vertex_group(self, context) - for ob in context.selected_objects: - ob.select = False - o.select = True - - if o.jv_is_soldier and o.jv_siding_types == "5" and o.jv_is_cutout: - soldier.select = True - - if len(mats) < 2: - mat = bpy.data.materials.new("siding_" + o.name) - mat.use_nodes = True - o.data.materials.append(mat) - else: - mat = bpy.data.materials.get(mats[0]) - o.data.materials.append(mat) - - mortar_ob.select = True - bpy.context.scene.objects.active = o - bpy.ops.object.join() - - # join battens if needed - elif v: - for ob in context.selected_objects: - ob.select = False - - battens.select, o.select = True, True - bpy.context.scene.objects.active = o - bpy.ops.object.join() - - for i in mats: - if i not in o.data.materials: - mat = bpy.data.materials.get(i) - o.data.materials.append(mat) - - if o.jv_is_unwrap: - unwrap_object(self, context) - if o.jv_is_random_uv: - random_uvs(self, context) - - -def siding_materials(self, context): - o = bpy.context.object - mat, mat2 = None, None - - if o.mat in ("2", "3", "4"): # vinyl, tin, fiber cement - rough = 0.3 if o.jv_siding_types == "2" else 0.18 if o.jv_siding_types == "3" else 0.35 - mat = glossy_diffuse_material(bpy, o.jv_rgba_color, (1.0, 1.0, 1.0), rough, 0.05, "siding_use") - elif o.jv_siding_types == "1" or (o.jv_siding_types == "6" and o.jv_sb_mat_type == "1"): # wood or stone with image - if o.jv_col_image == "": - self.report({"ERROR"}, "JARCH Vis: No Color Image Entered") - return - elif o.jv_is_bump and o.jv_norm_image == "": - self.report({"ERROR"}, "JARCH Vis: No Normal Map Image Entered") - return - - mat = image_material(bpy, o.jv_im_scale, o.jv_col_image, o.jv_norm_image, o.jv_bump_amo, o.jv_is_bump, - "siding_use", False, 1.0, 1.0, o.jv_is_rotate, None) - if mat is not None: - if o.jv_siding_types == "6" and len(o.data.materials) >= 2: - o.data.materials[0] = mat - o.data.materials[0].name = "siding_" + o.name - elif len(o.data.materials) < 2 and o.jv_siding_types == "6": - self.report({"ERROR"}, "JARCH Vis: Material Needed For Stone Not Found") - return - else: - self.report({"ERROR"}, "JARCH Vis: Images Not Found, Make Sure Path Is Correct") - return - # bricks or stone with built-in materials - elif o.jv_siding_types == "5" or (o.jv_siding_types == "6" and o.jv_sb_mat_type == "2"): - if len(o.data.materials) >= 2: - mat = brick_material(bpy, o.jv_color_style, o.jv_rgba_color, o.jv_color2, o.jv_color3, o.jv_color_sharp, - o.jv_color_scale, o.jv_bump_type, o.jv_brick_bump, o.jv_bump_scale, "siding_use") - o.data.materials[0] = mat - o.data.materials[0].name = "siding_" + o.name - else: - self.report({"ERROR"}, "JARCH Vis: Material Needed For Bricks//Stones Not Found, Please Update Siding") - return - - if o.jv_siding_types in ("5", "6"): # mortar - if len(o.data.materials) >= 2: - mname = "mortar_temp" if "mortar_temp" in o.data.materials else o.data.materials[1].name - mat2 = mortar_material(bpy, o.jv_mortar_color, o.jv_mortar_bump, mname) - o.data.materials[1] = mat2 - o.data.materials[1].name = "mortar_" + o.name - else: - self.report({"ERROR"}, "JARCH Vis: Material Needed For Mortar Not Found, Please Update Siding") - return - - # set material - if len(o.data.materials) >= 1 and mat is not None: - mat.name = o.name + "_siding" - o.data.materials[0] = mat - elif mat is not None: - mat.name = o.name + "_siding" - o.data.materials.append(mat) - elif mat is None: - self.report({"ERROR"}, "JARCH Vis: Material Could Not Be Created, Possibly Missing Images") - - if len(o.data.materials) >= 2 and o.jv_siding_types in ("5", "6") and mat2 is not None: - mat2.name = o.name + "_mortar" - o.data.materials[1] = mat2 - elif mat2 is not None: - mat2.name = o.name + "_mortar" - o.data.append(mat2) - elif mat is None and o.mat in ("5", "6"): - self.report({"ERROR"}, "JARCH Vis: Mortar Material Could Not Be Created, Possibly Missing Images") - - for i in bpy.data.materials: - if not i.users: - bpy.data.materials.remove(i) - - -def vertex_group(self, context): - o = context.object - for i in context.selected_objects: - i.select = False - o.select = True - bpy.context.scene.objects.active = o - - for area in bpy.context.screen.areas: - if area.type == 'VIEW_3D': - for region in area.regions: - if region.type == 'WINDOW': - bpy.ops.object.editmode_toggle() - if "JARCH" in o.vertex_groups: - group = o.vertex_groups.get("JARCH") - o.vertex_groups.remove(group) - bpy.ops.object.vertex_group_add() - bpy.ops.object.vertex_group_assign() - active = o.vertex_groups.active - active.name = "JARCH" - bpy.ops.object.editmode_toggle() - - -class SidingUpdate(bpy.types.Operator): - bl_idname = "mesh.jv_siding_update" - bl_label = "Update Siding" - bl_description = "Update Siding, Specifically For Updating Stone" - bl_options = {"UNDO", "INTERNAL"} - - def execute(self, context): - update_siding(self, context) - return {"FINISHED"} - - -class SidingDelete(bpy.types.Operator): - bl_idname = "mesh.jv_siding_delete" - bl_label = "Delete Siding" - bl_options = {"UNDO", "INTERNAL"} - - def execute(self, context): - o = context.object - cutter = None - - if o.jv_object_add == "convert" and o.jv_cut_name in bpy.data.objects: - cutter = bpy.data.objects[o.jv_cut_name] - o.select, cutter.select = False, True - context.scene.objects.active = cutter - - pre_layers = [i for i in bpy.context.scene.layers] - al = context.scene.active_layer - context.scene.layers = [i >= 19 for i in range(20)] - bpy.ops.object.transform_apply(location=False, rotation=True, scale=False) - bpy.ops.object.modifier_remove(modifier="Solidify") - bpy.ops.object.move_to_layer(layers=[i == al for i in range(20)]) - cutter.name = o.name - - cutter.jv_object_add = "none" - cutter.jv_internal_type = "" - cutter.select = False - context.scene.layers = pre_layers - - o.select = True - bpy.context.scene.objects.active = o - bpy.ops.object.delete() - - if cutter is not None: # select cutter object if it exists - cutter.select = True - context.scene.objects.active = cutter - - for i in bpy.data.materials: - if i.users == 0: - bpy.data.materials.remove(i) - - return {"FINISHED"} - - -class SidingMaterials(bpy.types.Operator): - bl_idname = "mesh.jv_siding_materials" - bl_label = "Generate\\Update Materials" - bl_options = {"UNDO", "INTERNAL"} - - def execute(self, context): - siding_materials(self, context) - return {"FINISHED"} - - -class SidingPanel(bpy.types.Panel): - bl_idname = "OBJECT_PT_jv_siding" - bl_label = "JARCH Vis: Siding" - bl_space_type = "VIEW_3D" - bl_region_type = "TOOLS" - bl_category = "JARCH Vis" - - def draw(self, context): - layout = self.layout - if bpy.context.mode == "EDIT_MESH": - layout.label("JARCH Vis: Siding Doesn't Work In Edit Mode", icon="ERROR") - else: - o = context.object - if o is not None and o.jv_internal_type == "siding" and o.type == "MESH": - layout.label("Material:") - layout.prop(o, "jv_siding_types", icon="MATERIAL") - layout.label("Type(s):") - - if o.jv_siding_types == "1": - layout.prop(o, "jv_wood_siding_types", icon="OBJECT_DATA") - elif o.jv_siding_types == "2": - layout.prop(o, "jv_vinyl_siding_types", icon="OBJECT_DATA") - elif o.jv_siding_types == "3": - layout.prop(o, "jv_tin_siding_types", icon="OBJECT_DATA") - elif o.jv_siding_types == "4": - layout.label("Horizontal: Lap", icon="OBJECT_DATA") - elif o.jv_siding_types == "5": - layout.label("Bricks", icon="OBJECT_DATA") - elif o.jv_siding_types == "6": - layout.label("Stone", icon="OBJECT_DATA") - layout.separator() - - if o.jv_object_add == "add": - layout.prop(o, "jv_over_width") - layout.prop(o, "jv_over_height") - layout.separator() - - if o.jv_siding_types not in ("3", "5", "6"): - layout.prop(o, "jv_b_width") - elif o.jv_siding_types == "3": - layout.label("Sheet Lays: 36 (in)", icon="ARROW_LEFTRIGHT") - layout.prop(o, "jv_is_screws", icon="PLUS") - - if o.jv_siding_types not in ("5", "6"): # if not bricks or stone - if o.jv_siding_types == "1" and o.jv_wood_siding_types in ("1", "2"): - layout.prop(o, "jv_spacing") - layout.separator() - if o.jv_siding_types in ("1", "2") and ((o.jv_vinyl_siding_types == "1" and - o.jv_siding_types == "2") - or (o.jv_wood_siding_types == "3" - and o.jv_siding_types == "1")): - layout.prop(o, "jv_batten_width") - if o.jv_batten_width / 2 > (o.jv_b_width / 2) - (0.125 / METRIC_INCH): - layout.label("Max Width: " + str(round(2 * ((o.jv_b_width / 2) - - (0.125 / METRIC_INCH)), 3)) + - " in", icon="ERROR") - elif o.jv_siding_types == "5": # bricks - layout.prop(o, "jv_br_width") - layout.prop(o, "jv_br_height") - layout.separator() - if o.jv_object_add == "add": - layout.prop(o, "jv_is_corner", icon="VIEW3D") - if not o.jv_is_corner: - layout.separator() - layout.prop(o, "jv_br_ran_offset", icon="NLA") - if not o.jv_br_ran_offset: - layout.prop(o, "jv_br_offset") - else: - layout.prop(o, "jv_br_vary") - else: - layout.separator() - layout.prop(o, "jv_is_left", icon="TRIA_LEFT") - layout.prop(o, "jv_is_right", icon="TRIA_RIGHT") - layout.prop(o, "jv_is_invert", icon="FILE_REFRESH") - layout.separator() - layout.prop(o, "jv_br_gap") - layout.separator() - layout.prop(o, "jv_br_m_depth") - layout.separator() - - if o.jv_object_add == "convert": - layout.prop(o, "jv_x_offset") - layout.separator() - - if o.jv_siding_types in ("5", "6") or (o.jv_siding_types == "1" and - o.jv_wood_siding_types == "1"): - layout.prop(o, "jv_is_bevel", icon="MOD_BEVEL") - if o.jv_is_bevel and o.jv_siding_types != "1": - layout.prop(o, "jv_bevel_res", icon="OUTLINER_DATA_CURVE") - layout.separator() - elif o.jv_siding_types == "1" and o.jv_is_bevel: - layout.prop(o, "jv_bevel_width") - - if o.jv_siding_types == "6": # stone - layout.prop(o, "jv_av_width") - layout.prop(o, "jv_av_height") - layout.separator() - layout.prop(o, "jv_random_size") - layout.prop(o, "jv_random_bump") - layout.separator() - layout.prop(o, "jv_br_gap") - layout.prop(o, "jv_st_m_depth") - layout.separator() - - if o.jv_object_add == "add": - layout.prop(o, "jv_is_slope", icon="TRIA_UP") - if o.jv_is_slope: - layout.label("Pitch x/12", icon="LINCURVE") - layout.prop(o, "jv_slope") - units = " m" - if o.jv_is_corner: - ht = round(o.jv_over_height - ((o.jv_slope * (o.jv_over_width / 2)) / 12), 2) - if ht <= 0: - slope = round(((24 * o.jv_over_height) / o.jv_over_width) - 0.01, 2) - ht = round(o.jv_over_height - ((slope * (o.jv_over_width / 2)) / 12), 2) - layout.label("Max Slope: " + str(slope), icon="ERROR") - else: - ht = round(o.jv_over_height - ((o.jv_slope * ((o.jv_over_width + - (2 * o.jv_br_width)) / 2)) / 12), 2) - if ht <= 0: - slope = round(((24 * o.jv_over_height) / o.jv_over_width + (2 * o.jv_br_width)) - - 0.01, 2) - ht = round(o.jv_over_height - ((slope * ((o.jv_over_width + - (2 * o.jv_br_width)) / 2)) / 12), 2) - layout.label("Max Slope: " + str(slope), icon="ERROR") - if context.scene.unit_settings.system == "IMPERIAL": - ht = round(METRIC_FOOT * ht, 2) - units = " ft" - layout.label("Height At Edges: " + str(ht) + units, icon="TEXT") - - if o.jv_siding_types not in ("5", "6"): - if o.jv_siding_types == "1" and o.jv_wood_siding_types == "1": - layout.prop(o, "jv_is_width_vary", icon="UV_ISLANDSEL") - if o.jv_is_width_vary: - layout.prop(o, "jv_width_vary") - if o.jv_siding_types != "3": - layout.prop(o, "jv_is_length_vary", icon="NLA") - if o.jv_is_length_vary: - layout.prop(o, "jv_length_vary") - layout.prop(o, "jv_max_boards") - if o.jv_siding_types == "2": - layout.separator() - layout.prop(o, "jv_bevel_res", icon="OUTLINER_DATA_CURVE") - layout.separator() - - if o.jv_object_add == "add": - layout.prop(o, "jv_is_cutout", icon="MOD_BOOLEAN") - if o.jv_is_cutout: - if o.jv_siding_types == "5": - layout.separator() - layout.prop(o, "jv_is_soldier", icon="DOTSUP") - - layout.separator() - layout.template_list("OBJECT_UL_jv_cutout_groups", "", o, "jv_cutout_groups", o, - "jv_cutout_group_index") - layout.separator() - row = layout.row() - row.prop(o, "jv_cutout_x") - row.prop(o, "jv_cutout_z") - row = layout.row() - row.prop(o, "jv_cutout_width") - row.prop(o, "jv_cutout_height") - row = layout.row() - row.operator("mesh.jv_add_cutout_item", icon="ZOOMIN") - row.operator("mesh.jv_remove_cutout_item", icon="ZOOMOUT") - row.operator("mesh.jv_update_cutout_item", icon="FILE_REFRESH") - - layout.separator() - layout.prop(o, "jv_is_unwrap", icon="GROUP_UVS") - if o.jv_is_unwrap: - layout.prop(o, "jv_is_random_uv", icon="RNDCURVE") - layout.separator() - - # materials - if context.scene.render.engine == "CYCLES": - layout.prop(o, "jv_is_material", icon="MATERIAL") - else: - layout.label("Materials Only Supported With Cycles", icon="POTATO") - layout.separator() - - if o.jv_is_material and context.scene.render.engine == "CYCLES": - if o.jv_siding_types == "6": - layout.prop(o, "jv_sb_mat_type") - layout.separator() - if o.jv_siding_types in ("2", "3", "4"): - layout.prop(o, "jv_rgba_color", icon="COLOR") # vinyl and tin - # wood and fiber cement - elif o.jv_siding_types == "1" or (o.jv_siding_types == "6" and o.jv_sb_mat_type == "1"): - layout.prop(o, "jv_col_image", icon="COLOR") - layout.prop(o, "jv_is_bump", icon="SMOOTHCURVE") - - if o.jv_is_bump: - layout.prop(o, "jv_norm_image", icon="TEXTURE") - layout.prop(o, "jv_bump_amo") - - layout.prop(o, "jv_im_scale", icon="MAN_SCALE") - layout.prop(o, "jv_is_rotate", icon="MAN_ROT") - # bricks or stone - elif o.jv_siding_types == "5" or (o.jv_siding_types == "6" and o.jv_sb_mat_type == "2"): - layout.prop(o, "jv_color_style", icon="COLOR") - layout.prop(o, "jv_rgba_color", icon="COLOR") - - if o.jv_color_style != "constant": - layout.prop(o, "jv_color2", icon="COLOR") - if o.jv_color_style == "extreme": - layout.prop(o, "jv_color3", icon="COLOR") - - layout.prop(o, "jv_color_sharp") - layout.prop(o, "jv_color_scale") - layout.separator() - layout.prop(o, "jv_mortar_color", icon="COLOR") - layout.prop(o, "jv_mortar_bump") - layout.prop(o, "jv_bump_type", icon="SMOOTHCURVE") - - if o.jv_bump_type != "4": - layout.prop(o, "jv_brick_bump") - layout.prop(o, "jv_bump_scale") - - if o.jv_siding_types == "6": - layout.separator() - layout.prop(o, "jv_mortar_color", icon="COLOR") - layout.prop(o, "jv_mortar_bump") - layout.prop(o, "jv_bump_scale") - - layout.separator() - layout.operator("mesh.jv_siding_materials", icon="MATERIAL") - layout.prop(o, "jv_is_preview", icon="SCENE") - - layout.separator() - layout.separator() - layout.operator("mesh.jv_siding_update", icon="FILE_REFRESH") - layout.operator("mesh.jv_siding_delete", icon="CANCEL") - layout.operator("mesh.jv_siding_add", icon="UV_ISLANDSEL") - else: - if o is not None and o.jv_internal_type not in ("siding", ""): - layout.label("This Is Already A JARCH Vis Object", icon="INFO") - elif o is not None and o.jv_internal_type == "" and o.type == "MESH": - layout.operator("mesh.jv_siding_convert", icon="FILE_REFRESH") - layout.operator("mesh.jv_siding_add", icon="UV_ISLANDSEL") - - -class SidingAdd(bpy.types.Operator): - bl_idname = "mesh.jv_siding_add" - bl_label = "Add Siding" - bl_description = "JARCH Vis: Siding Generator" - - @classmethod - def poll(cls, context): - return context.mode == "OBJECT" - - def execute(self, context): - bpy.ops.mesh.primitive_cube_add() - o = bpy.context.scene.objects.active - o.jv_internal_type = "siding" - o.jv_object_add = "add" - return {"FINISHED"} - - -class SidingConvert(bpy.types.Operator): - bl_idname = "mesh.jv_siding_convert" - bl_label = "Convert To Siding" - bl_options = {"UNDO", "INTERNAL"} - - def execute(self, context): - o = context.object - o.jv_internal_type = "siding" - o.jv_object_add = "convert" - return {"FINISHED"} - - -class OBJECT_UL_jv_cutout_groups(bpy.types.UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): - row = layout.row(align=True) - row.label(str(index + 1), icon="FULLSCREEN") - row.label("X: " + str(round(item.x_dist, 2))) - row.label("Z: " + str(round(item.z_dist, 2))) - row.label("Width: " + str(round(item.width, 2))) - row.label("Height: " + str(round(item.height, 2))) - - -class CGAddItem(bpy.types.Operator): - bl_idname = "mesh.jv_add_cutout_item" - bl_label = "Add" - bl_options = {"UNDO", "INTERNAL"} - - def execute(self, context): - o = context.object - cutout = o.jv_cutout_groups.add() - cutout.x_dist = o.jv_cutout_x - cutout.z_dist = o.jv_cutout_z - cutout.width = o.jv_cutout_width - cutout.height = o.jv_cutout_height - o.jv_cutout_group_ct += 1 - o.jv_cutout_group_index = len(o.jv_cutout_groups) - 1 - update_siding(self, context) - return {"FINISHED"} - - -class CGRemoveItem(bpy.types.Operator): - bl_idname = "mesh.jv_remove_cutout_item" - bl_label = "Remove" - bl_options = {"UNDO", "INTERNAL"} - - def execute(self, context): - ob = context.object - if len(ob.jv_cutout_groups) > 0: - ob.jv_cutout_groups.remove(context.object.jv_cutout_group_index) - ob.jv_cutout_group_ct = len(ob.jv_cutout_groups) - - if len(ob.jv_cutout_groups) == 0: - ob.jv_cutout_group_index = 0 - else: - ob.jv_cutout_group_index = len(ob.jv_cutout_groups) - 1 - update_siding(self, context) - return {"FINISHED"} - - -class CGUpdateItem(bpy.types.Operator): - bl_idname = "mesh.jv_update_cutout_item" - bl_label = "Update" - bl_options = {"UNDO", "INTERNAL"} - - def execute(self, context): - o = context.object - if len(o.jv_cutout_groups) > 0 and 0 <= o.jv_cutout_group_index < len(o.jv_cutout_groups): - cutout = o.jv_cutout_groups[o.jv_cutout_group_index] - cutout.x_dist = o.jv_cutout_x - cutout.z_dist = o.jv_cutout_z - cutout.width = o.jv_cutout_width - cutout.height = o.jv_cutout_height - update_siding(self, context) - return {"FINISHED"} - - -# class CutoutGroup(bpy.types.PropertyGroup): -# x_dist = FloatProperty(subtype="DISTANCE") -# z_dist = FloatProperty(subtype="DISTANCE") -# width = FloatProperty(subtype="DISTANCE") -# height = FloatProperty(subtype="DISTANCE") - - -# def register(): -# bpy.utils.register_module(__name__) -# bpy.types.Object.jv_cutout_groups = CollectionProperty(type=CutoutGroup) -# -# -# def unregister(): -# bpy.utils.unregister_module(__name__) -# del bpy.types.Object.jv_cutout_groups -# -# if __name__ == "__main__": -# register() + x += width + gap + odd = not odd + z += rest_z diff --git a/jv_stairs.py b/jv_stairs.py deleted file mode 100644 index e210071..0000000 --- a/jv_stairs.py +++ /dev/null @@ -1,1263 +0,0 @@ -# This file is part of JARCH Vis -# -# JARCH Vis is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# JARCH Vis is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with JARCH Vis. If not, see . - -import bpy -from bpy.props import BoolProperty, EnumProperty, FloatProperty, StringProperty, IntProperty, FloatVectorProperty -from math import radians, tan, sqrt, asin -from mathutils import Euler, Vector -import bmesh -from . jv_materials import image_material -from . jv_utils import point_rotation, METRIC_INCH, I, unwrap_object, random_uvs -# import jv_properties - - -def custom_tread_placement(context, custom_tread_pos, custom_tread): - # custom treads - if custom_tread_pos: - if custom_tread in bpy.data.objects: - tread_ob = bpy.data.objects[custom_tread] - ob = context.object - - for i in context.selected_objects: - i.select = False - - tread_names = [] - for i in custom_tread_pos: - tread_ob.select = True - context.scene.objects.active = tread_ob - - bpy.ops.object.duplicate() - context.object.location = i - context.object.hide = False - tread_names.append(context.object.name) - context.object.select = False - tread_ob.select = False - - for i in tread_names: - bpy.data.objects[i].select = True - - ob.select = True - context.scene.objects.active = ob - bpy.ops.object.join() - - -def create_stairs(self, context, style, overhang, steps, tw, rh, of, os, w, n_landing, is_close, tw0, rh0, ld0, l_rot0, - of0, os0, overhang0, is_back0, l_rot1, tw1, rh1, ld1, of1, os1, overhang1, is_back1, w_rot, - stair_to_rot, rot, steps0, steps1, set_in, is_riser, is_landing, is_light, num_steps2, tread_res, - pd, pole_res, is_custom_tread, custom_tread): - - verts, faces, names = [], [], [] - - # figure out what angle to use for winding stairs if them - angles = [None, radians(-90), radians(-45), radians(45), radians(90)] - angle = angles[int(w_rot)] - - mats = [] - for i in context.object.data.materials: - mats.append(i.name) - - if style == "1": - pre_pos = [0.0, 0.0, 0.0] - pre_rot = tuple(context.object.rotation_euler) - - # for each set of stairs and landings - for i in range(n_landing + 1): - - # calculate variables to pass in - if i == 0: - pass_in = [tw, rh, of, os, overhang, steps] - elif i == 1: - pass_in = [tw0, rh0, of0, os0, overhang0, steps0, l_rot0, ld0] - elif i == 2: - pass_in = [tw1, rh1, of1, os1, overhang1, steps1, l_rot1, ld1] - - # get step data - verts_t, faces_t, cx, cy, cz, verts2_t, faces2_t, tread_pos = normal_stairs(pass_in[0], pass_in[1], - pass_in[2], pass_in[3], w, - pass_in[4], pass_in[5], - is_close, set_in, is_riser, - is_light, i, is_custom_tread) - rot = 0 - # create jacks or wait till rotation and location is figured out depending on which level you are on - if i == 0: - m_ob = context.object - verts, faces = verts_t, faces_t - mesh4 = bpy.data.meshes.new("jacks_" + str(i)) - mesh4.from_pydata(verts2_t, [], faces2_t) - ob3 = bpy.data.objects.new("jacks_" + str(i), mesh4) - context.scene.objects.link(ob3) - - m_ob.select, ob3.select = False, True - context.scene.objects.active = ob3 - custom_tread_placement(context, tread_pos, custom_tread) - - ob3.select, m_ob.select = False, True - context.scene.objects.active = m_ob - - ob3.rotation_euler = context.object.rotation_euler - ob3.location = context.object.location - names.append(ob3.name) - ob3.scale = context.object.scale - - if context.scene.render.engine == "CYCLES": - if len(mats) >= 2: - ob3.data.materials.append(bpy.data.materials[mats[1]]) - else: - mat = bpy.data.materials.new("jack_temp") - mat.use_nodes = True - ob3.data.materials.append(mat) - else: - pre_pos2 = pre_pos[:] - - if l_rot0 == "2": - rot += 180 if is_back0 else 90 - elif l_rot0 == "3": - rot -= 180 if is_back0 else 90 - if n_landing == 2 and i == 2: - if l_rot1 == "2": - rot += 180 if is_back1 else 90 - elif l_rot1 == "3": - rot -= 180 if is_back1 else 90 - - # calculate position, adjust offset causes by rotating to the side - if pass_in[6] == "1": # forwards - orient = "straight" - if i == 1: - pre_pos[1] += pass_in[7] - elif i == 2 and l_rot0 == "1": - pre_pos[1] += pass_in[7] - elif i == 2 and l_rot0 == "2" and not is_back0: - pre_pos[0] -= pass_in[7] - elif i == 2 and l_rot0 == "2" and is_back0: - pre_pos[1] -= pass_in[7] - elif i == 2 and l_rot0 == "3" and not is_back0: - pre_pos[0] += pass_in[7] - elif i == 2 and l_rot0 == "3" and is_back0: - pre_pos[1] -= pass_in[7] - elif pass_in[6] == "2": # left - if i == 1 and not is_back0: - pre_pos[0] -= w / 2 - pre_pos[1] += pass_in[7] / 2 - orient = "straight" - elif i == 1 and is_back0: - pre_pos[0] -= w - orient = "left" - elif i == 2 and l_rot0 == "1" and not is_back1: - pre_pos[1] += pass_in[7] / 2 - pre_pos[0] -= w / 2 - orient = "straight" - elif i == 2 and l_rot0 == "1" and is_back1: - pre_pos[0] -= w - orient = "left" - elif i == 2 and l_rot0 == "2" and not is_back0 and not is_back1: - pre_pos[0] -= pass_in[7] / 2 - pre_pos[1] -= w / 2 - orient = "straight" - elif i == 2 and l_rot0 == "2" and not is_back0 and is_back1: - pre_pos[1] -= w - orient = "left" - elif i == 2 and l_rot0 == "2" and is_back0 and not is_back1: - pre_pos[0] += w / 2 - pre_pos[1] -= pass_in[7] / 2 - orient = "straight" - elif i == 2 and l_rot0 == "2" and is_back0 and is_back1: - pre_pos[0] += w - orient = "left" - elif i == 2 and l_rot0 == "3" and not is_back0 and not is_back1: - pre_pos[0] += pass_in[7] / 2 - pre_pos[1] += w / 2 - orient = "straight" - elif i == 2 and l_rot0 == "3" and not is_back0 and is_back1: - pre_pos[1] += w - orient = "left" - elif i == 2 and l_rot0 == "3" and is_back0 and not is_back1: - pre_pos[0] += w / 2 - pre_pos[1] -= pass_in[7] / 2 - orient = "straight" - elif i == 2 and l_rot0 == "3" and is_back0 and is_back1: - pre_pos[0] += w - orient = "left" - elif pass_in[6] == "3": - if i == 1 and not is_back0: - pre_pos[1] += pass_in[7] / 2 - pre_pos[0] += w / 2 - orient = "straight" - elif i == 1 and is_back0: - pre_pos[0] += w - orient = "right" - elif i == 2 and l_rot0 == "1" and not is_back1: - pre_pos[1] += pass_in[7] / 2 - pre_pos[0] += w / 2 - orient = "straight" - elif i == 2 and l_rot0 == "1" and is_back1: - pre_pos[0] += w - orient = "right" - elif i == 2 and l_rot0 == "2" and not is_back0 and not is_back1: - pre_pos[0] -= pass_in[7] / 2 - pre_pos[1] += w / 2 - orient = "straight" - elif i == 2 and l_rot0 == "2" and not is_back0 and is_back1: - pre_pos[1] += w - orient = "right" - elif i == 2 and l_rot0 == "2" and is_back0 and not is_back1: - pre_pos[0] -= w / 2 - pre_pos[1] -= pass_in[7] / 2 - orient = "straight" - elif i == 2 and l_rot0 == "2" and is_back0 and is_back1: - pre_pos[0] -= w - orient = "right" - elif i == 2 and l_rot0 == "3" and not is_back0 and not is_back1: - pre_pos[0] += pass_in[7] / 2 - pre_pos[1] -= w / 2 - orient = "straight" - elif i == 2 and l_rot0 == "3" and not is_back0 and is_back1: - pre_pos[1] -= w - orient = "right" - elif i == 2 and l_rot0 == "3" and is_back0 and not is_back1: - pre_pos[0] -= w / 2 - pre_pos[1] -= pass_in[7] / 2 - orient = "straight" - elif i == 2 and l_rot0 == "3" and is_back0 and is_back1: - pre_pos[0] -= w - orient = "right" - - # create stairs - pre_pos[2] += 1 / METRIC_INCH - mesh2 = bpy.data.meshes.new("stair_" + str(i)) - mesh2.from_pydata(verts_t, [], faces_t) - ob = bpy.data.objects.new("stair_" + str(i), mesh2) - context.scene.objects.link(ob) - - m_ob = context.object - - m_ob.select, ob.select = False, True - context.scene.objects.active = ob - custom_tread_placement(context, tread_pos, custom_tread) - - ob.select, m_ob.select = False, True - context.scene.objects.active = m_ob - - o = context.object - eur = o.rotation_euler.copy() - eur2 = Euler((0.0, 0.0, radians(rot))) - eur.rotate(eur2) - # matrix = o.matrix_world.inverted() - pos = list(o.matrix_world * Vector(pre_pos)) # * matrix - pos[0] += o.location[0] - pos[1] += o.location[1] - pos[2] += o.location[2] - names.append(ob.name) - ob.rotation_euler, ob.location, ob.scale = eur, pos, o.scale - - # jacks - mesh4 = bpy.data.meshes.new("jacks_" + str(i)) - mesh4.from_pydata(verts2_t, [], faces2_t) - ob3 = bpy.data.objects.new("jacks_" + str(i), mesh4) - context.scene.objects.link(ob3) - ob3.rotation_euler, ob3.location, ob3.scale = eur, pos, o.scale - names.append(ob3.name) - - if context.scene.render.engine == "CYCLES": - if len(mats) >= 2: - ob3.data.materials.append(bpy.data.materials[mats[1]]) - else: - mat = bpy.data.materials.new("jack_temp") - mat.use_nodes = True - ob3.data.materials.append(mat) - - # landings - if is_landing: - pre_pos2[2] += 1 / METRIC_INCH - verts2, faces2 = stair_landing(w, pass_in[7], pass_in[1], orient) - mesh3 = bpy.data.meshes.new("landing_" + str(i)) - mesh3.from_pydata(verts2, [], faces2) - ob2 = bpy.data.objects.new("landing_" + str(i), mesh3) - context.scene.objects.link(ob2) - names.append(ob2.name) - pre_pos2[2] -= pass_in[1] - pos2 = list(o.matrix_world * Vector(pre_pos2)) - pos2[0] += o.location[0] - pos2[1] += o.location[1] - pos2[2] += o.location[2] - ob2.rotation_euler, ob2.location, ob2.scale = pre_rot, pos2, o.scale - pre_rot = eur - - # Apply translations correctly in relation to the rotation of the stairs - if rot == 0 or rot == 360: - pre_pos = [cx + pre_pos[0], cy + pre_pos[1], cz + pre_pos[2]] - elif rot == 90 or rot == -270: - pre_pos = [pre_pos[0] - cy, pre_pos[1], pre_pos[2] + cz] - elif rot == -90 or rot == 270: - pre_pos = [pre_pos[0] + cy, pre_pos[1], pre_pos[2] + cz] - elif rot == 180 or rot == -180: - pre_pos = [pre_pos[0], pre_pos[1] - cy, pre_pos[2] + cz] - elif style == "2": - verts, faces = winding_stairs(tw, rh, of, w, steps, angle, stair_to_rot) - elif style == "3": - verts, faces, verts2, faces2 = spiral_stairs(w, num_steps2, rh, rot, of, tread_res, pd, pole_res) - mesh = bpy.data.meshes.new("pole_temp") - mesh.from_pydata(verts2, [], faces2) - ob4 = bpy.data.objects.new("pole_temp", mesh) - context.scene.objects.link(ob4) - o = context.object - ob4.location, ob4.rotation_euler, ob4.scale = o.location, o.rotation_euler, o.scale - names.append(ob4.name) - - if context.scene.render.engine == "CYCLES": - if len(mats) >= 2: - ob4.data.materials.append(bpy.data.materials[mats[1]]) - else: - mat = bpy.data.materials.new("pole_temp") - mat.use_nodes = True - ob4.data.materials.append(mat) - - return verts, faces, names - - -def spiral_stairs(w, steps, rh, rot, of, tread_res, pd, pole_res): - verts, faces = [], [] - ang = rot / (steps - 1) - cur_ang, hof, cz = 0.0, of / 2, rh - I - - # half of the tread width out at the end - ih = of / (tread_res + 1) - for step in range(steps - 1): - p = len(verts) - - points = [(0.0, -hof), (w, 0.0)] - for i in range(tread_res): - points.append((w, 0.0)) - points += ((w, 0.0), (0.0, hof)) - for i in range(tread_res): - points.append((0.0, hof - (i * ih) - ih)) - - ct = 0 - for i in points: - e_rot = asin(of / w) if step != 0 else 0 - - if ct == 1 and rot >= 0: - angle = cur_ang - e_rot - elif ct == 2 + tread_res and rot >= 0: - angle = cur_ang + ang - elif ct == 1 and rot < 0: - angle = cur_ang + ang - elif ct == 2 + tread_res and rot < 0: - angle = cur_ang - elif 1 < ct < 2 + tread_res and rot >= 0: - angle = cur_ang + (((ang + e_rot) / (tread_res + 1)) * (ct - 1)) - e_rot - elif 1 < ct < 2 + tread_res and rot < 0: - angle = (cur_ang + ang) - (((ang + e_rot) / (tread_res + 1)) * (ct - 1)) - e_rot - else: - angle = -(step * ang + radians(180)) - ang / 2 - - x, y = point_rotation(i, (0.0, 0.0), angle) - verts += [(x, y, cz), (x, y, cz + I)] - ct += 1 - - cz += rh - cur_ang += ang - - lp = p - for i in range(3 + 2 * tread_res): # edge faces - faces.append((lp, lp + 2, lp + 3, lp + 1)) - lp += 2 - faces.append((p, p + 1, lp + 1, lp + 1, lp)) # last side face - - p1, p2 = 1, 3 - for i in range(1 + tread_res): - p3 = (p1 - 2) % (8 + tread_res * 4) - faces.append((p + p1, p + p2, p + p2 + 2, p + p3)) - p1, p2 = p3, p2 + 2 - - p1, p2 = 0, 2 - for i in range(1 + tread_res): - p3 = (p1 - 2) % (8 + tread_res * 4) - faces.append((p + p1, p + p3, p + p2 + 2, p + p2)) - p1, p2 = p3, p2 + 2 - - # pole - cz -= rh - I - verts2, faces2 = [], [] - ang = radians(360 / pole_res) - z = 0.0 - - for i in range(2): - for vs in range(pole_res): - cur_ang = vs * ang - x, y = point_rotation((pd / 2, 0.0), (0.0, 0.0), cur_ang) - verts2.append((x, y, z)) - z += cz - - for i in range(pole_res - 1): - faces2.append((i, i + 1, i + pole_res + 1, i + pole_res)) - faces2.append((pole_res - 1, 0, pole_res, 2 * pole_res - 1)) # last side face - faces2.append([i for i in range(0, pole_res)]) # bottom - faces2.append([i for i in range(pole_res, pole_res * 2)]) # top - - return verts, faces, verts2, faces2 - - -def winding_stairs(tw, rh, of, w, steps, w_rot, st_t_rot): - verts, faces = [], [] - # create radians measures and round to 4 decimal places - r45, r90, r180 = radians(45), radians(90), radians(180) - tw -= of - - # figure out the distance farther to go on left or right side based on rotation - if -r45 < w_rot < r45: - gy, ex = abs(w * tan(w_rot)), 0 - else: - gy, ex = w, w * tan(abs(w_rot) - r45) - gy += I - - # calculate number of steps on corner - if w_rot != 0.0: - ti = 10 / METRIC_INCH - c_steps = int((ti * abs(w_rot)) / (tw - I)) - else: - c_steps = 0 - - cx, cy, cz, hw = 0, 0, 0, w / 2 - rh -= I - temp_dw, temp_x = sqrt((gy ** 2 + w ** 2)), 0.0 - - for step in range(steps): - p = len(verts) - face_type = None - - if step + 1 < st_t_rot: # before rotation - verts += [(cx - hw, cy, cz), (cx - hw, cy + I, cz), (cx - hw, cy + I, cz + rh), (cx - hw, cy, cz + rh), - (cx + hw, cy, cz), (cx + hw, cy + I, cz), (cx + hw, cy + I, cz + rh), (cx + hw, cy, cz + rh), - (cx - hw, cy - of, cz + rh), (cx - hw, cy + tw, cz + rh), (cx - hw, cy + tw, cz + rh + I), - (cx - hw, cy - of, cz + rh + I), (cx + hw, cy - of, cz + rh), (cx + hw, cy + tw, cz + rh), - (cx + hw, cy + tw, cz + rh + I), (cx + hw, cy - of, cz + rh + I)] - cy += tw - cz += rh - face_type = "normal" - elif st_t_rot <= step + 1 <= st_t_rot + c_steps: # in rotation - if -r45 <= w_rot <= r45: - yp = (gy / (c_steps + 1)) - y, y2 = yp * (step + 2 - st_t_rot), yp * (step + 1 - st_t_rot) - if 0 < w_rot <= r45: # positive rotation - cx = hw - verts += [(cx, cy, cz), (cx, cy, cz + rh), (cx - w, cy + y2, cz), (cx - w, cy + y2, cz + rh), - (cx - w, cy + y2 + I, cz), (cx - w, cy + y2 + I, cz + rh), (cx, cy + I, cz), - (cx, cy + I, cz + rh), (cx, cy - of, cz + rh), (cx, cy - of, cz + rh + I), - (cx - w, cy + y2 - of, cz + rh), (cx - w, cy + y2 - of, cz + rh + I), - (cx - w, cy + y, cz + rh), (cx - w, cy + y, cz + rh + I), (cx, cy + I, cz + rh), - (cx, cy + I, cz + rh + I)] - face_type = "pos" - elif -r45 <= w_rot < 0: # negative rotation - cx = -hw - verts += [(cx, cy, cz), (cx, cy, cz + rh), (cx + w, cy + y2, cz), (cx + w, cy + y2, cz + rh), - (cx + w, cy + y2 + I, cz), (cx + w, cy + y2 + I, cz + rh), (cx, cy + I, cz), - (cx, cy + I, cz + rh), (cx, cy - of, cz + rh), (cx, cy - of, cz + rh + I), - (cx + w, cy + y2 - of, cz + rh), (cx + w, cy + y2 - of, cz + rh + I), - (cx + w, cy + y, cz + rh), (cx + w, cy + y, cz + rh + I), (cx, cy + I, cz + rh), - (cx, cy + I, cz + rh + I)] - face_type = "neg" - else: # |w_rot| > 45 - ang = w_rot / (c_steps + 1) - cur_ang = ang * (step + 2 - st_t_rot) - if w_rot > 0: # positive rotation - if abs(cur_ang) <= r45: - y, y2 = (gy / r45) * abs(cur_ang), (gy / r45) * (abs(cur_ang - ang)) - cx = hw - verts += [(cx, cy, cz), (cx, cy, cz + rh), (cx - w, cy + y2, cz), (cx - w, cy + y2, cz + rh), - (cx - w, cy + y2 + I, cz), (cx - w, cy + y2 + I, cz + rh), (cx, cy + I, cz), - (cx, cy + I, cz + rh), (cx, cy - of, cz + rh), (cx, cy - of, cz + rh + I), - (cx - w, cy - of, cz + rh), (cx - w, cy - of, cz + rh + I), (cx - w, cy + y, cz + rh), - (cx - w, cy + y, cz + rh + I), (cx, cy + I, cz + rh), (cx, cy + I, cz + rh + I)] - face_type = "pos" - elif abs(cur_ang - ang) < r45 < abs(cur_ang): # step on corner - x = (ex / (abs(w_rot) - r45)) * (abs(cur_ang) - r45) - I - y2, temp_x, cx = (gy / r45) * (abs(cur_ang - ang)), x, hw - verts += [(cx, cy, cz), (cx, cy, cz + rh), (cx - w, cy + y2, cz), (cx - w, cy + y2, cz + rh), - (cx - w, cy + y2 + I, cz), (cx - w, cy + y2 + I, cz + rh), (cx, cy + I, cz), - (cx, cy + I, cz + rh), (cx, cy - of, cz + rh), (cx, cy - of, cz + rh + I), - (cx - w, cy - of + y2, cz + rh), (cx - w, cy - of + y2, cz + rh + I), - (cx - w, cy + gy, cz + rh), (cx - w, cy + gy, cz + rh + I), - (cx - w + x, cy + gy, cz + rh), (cx - w + x, cy + gy, cz + rh + I), (cx, cy, cz + rh), - (cx, cy, cz + rh + I)] - face_type = "pos_tri" - else: # last step - ct = 0 - points = ((cx, cy), (cx - w + temp_x - I, cy + gy - I), (cx - w + temp_x - I, cy + gy), - (cx - I, cy), (cx + of, cy), (cx - w + temp_x - I, cy + gy - of - I), - (cx - w + temp_x - I, cy + gy + ex - temp_x - I), (cx - I, cy)) - - for i in points: - origin = (cx - w + temp_x, cy + gy - I) if ct in (1, 2, 5, 6) else (cx, cy) - x, y = point_rotation(i, origin, w_rot + r180) - - if ct <= 3: - verts += [(x, y, cz), (x, y, cz + rh)] - else: - verts += [(x, y, cz + rh), (x, y, cz + rh + I)] - ct += 1 - face_type = "pos" - else: # negative rotation - if abs(cur_ang) <= r45: - y, y2, cx = (gy / r45) * abs(cur_ang), (gy / r45) * (abs(cur_ang - ang)), -hw - verts += [(cx, cy, cz), (cx, cy, cz + rh), (cx + w, cy + y2, cz), (cx + w, cy + y2, cz + rh), - (cx + w, cy + y2 + I, cz), (cx + w, cy + y2 + I, cz + rh), (cx, cy + I, cz), - (cx, cy + I, cz + rh), (cx, cy - of, cz + rh), (cx, cy - of, cz + rh + I), - (cx + w, cy - of, cz + rh), (cx + w, cy - of, cz + rh + I), (cx + w, cy + y, cz + rh), - (cx + w, cy + y, cz + rh + I), (cx, cy + I, cz + rh), (cx, cy + I, cz + rh + I)] - face_type = "neg" - elif abs(cur_ang - ang) < r45 < abs(cur_ang): # step on corner - x = (ex / (abs(w_rot) - r45)) * (abs(cur_ang) - r45) + I - y2, temp_x, cx = (gy / r45) * (abs(cur_ang - ang)), x, -hw - verts += [(cx, cy, cz), (cx, cy, cz + rh), (cx + w, cy + y2, cz), (cx + w, cy + y2, cz + rh), - (cx + w, cy + y2 + I, cz), (cx + w, cy + y2 + I, cz + rh), (cx, cy + I, cz), - (cx, cy + I, cz + rh), (cx, cy - of, cz + rh), (cx, cy - of, cz + rh + I), - (cx + w, cy - of + y2, cz + rh), (cx + w, cy - of + y2, cz + rh + I), - (cx + w, cy + gy, cz + rh), (cx + w, cy + gy, cz + rh + I), - (cx + w - x, cy + gy, cz + rh), (cx + w - x, cy + gy, cz + rh + I), (cx, cy, cz + rh), - (cx, cy, cz + rh + I)] - face_type = "neg_tri" - else: # last step - ct = 0 - points = ((cx, cy), (cx + w - temp_x + I, cy + gy - I), (cx + w - temp_x + I, cy + gy), - (cx + I, cy), (cx - of, cy), (cx + w - temp_x + I, cy + gy - of - I), - (cx + w - temp_x + I, cy + gy + ex - temp_x - I), (cx + I, cy)) - - for i in points: - origin = (cx + w - temp_x, cy + gy - I) if ct in (1, 2, 5, 6) else (cx, cy) - x, y = point_rotation(i, origin, w_rot + r180) - - if ct <= 3: - verts += [(x, y, cz), (x, y, cz + rh)] - else: - verts += [(x, y, cz + rh), (x, y, cz + rh + I)] - ct += 1 - face_type = "neg" - cz += rh - else: # after rotation - if step == st_t_rot + c_steps: - cy += I - cz += rh + I - else: - cz += I - cs, ct = step - c_steps - st_t_rot, 0 - z = cz + (rh * cs) - - if w_rot > 0: - o2, face_type = (cx - w + ex, cy + gy - I), "pos" - points = ((cx, cy + (cs * tw) - of), (cx - w + ex, cy + (cs * tw) + gy - I - of), - (cx - w + ex, cy + (cs * tw) + tw + gy - I), (cx, cy + (cs * tw) + tw), (cx, cy + (cs * tw)), - (cx - w + ex, cy + (cs * tw) + gy - I), (cx - w + ex, cy + (cs * tw) + gy), - (cx, cy + (cs * tw) + I)) - else: - o2, face_type = (cx + w - ex, cy + gy - I), "neg" - points = ((cx, cy + (cs * tw) - of), (cx + w - ex, cy + (cs * tw) + gy - I - of), - (cx + w - ex, cy + (cs * tw) + tw + gy - I), (cx, cy + (cs * tw) + tw), (cx, cy + (cs * tw)), - (cx + w - ex, cy + (cs * tw) + gy - I), (cx + w - ex, cy + (cs * tw) + gy), - (cx, cy + (cs * tw) + I)) - for i in points: - origin = o2 if ct in (1, 2, 5, 6) else (cx, cy) - x, y = point_rotation(i, origin, w_rot + r180) - - if ct <= 3: - verts += [(x, y, z), (x, y, z + I)] - else: - verts += [(x, y, z - rh - I), (x, y, z)] - ct += 1 - - if face_type == "normal": - faces += [(p, p + 4, p + 7, p + 3), (p, p + 3, p + 2, p + 1), (p + 1, p + 2, p + 6, p + 5), - (p + 2, p + 3, p + 7, p + 6), (p, p + 4, p + 5, p + 1), (p + 4, p + 5, p + 6, p + 7), - (p + 8, p + 11, p + 10, p + 9), (p + 8, p + 12, p + 15, p + 11), (p + 12, p + 13, p + 14, p + 15), - (p + 14, p + 13, p + 9, p + 10), (p + 11, p + 15, p + 14, p + 10), (p + 8, p + 12, p + 13, p + 9)] - elif face_type == "pos": - for i in range(2): - faces += [(p, p + 6, p + 7, p + 1), (p, p + 1, p + 3, p + 2), (p + 2, p + 3, p + 5, p + 4), - (p + 4, p + 5, p + 7, p + 6), (p + 1, p + 7, p + 5, p + 3), (p, p + 2, p + 4, p + 6)] - p += 8 - elif face_type == "neg": - for i in range(2): - faces += [(p, p + 1, p + 7, p + 6), (p, p + 2, p + 3, p + 1), (p + 2, p + 4, p + 5, p + 3), - (p + 4, p + 6, p + 7, p + 5), (p + 1, p + 3, p + 5, p + 7), (p, p + 6, p + 4, p + 2)] - p += 8 - elif face_type == "pos_tri": - faces += [(p, p + 6, p + 7, p + 1), (p, p + 1, p + 3, p + 2), (p + 2, p + 3, p + 5, p + 4), - (p + 4, p + 5, p + 7, p + 6), (p + 1, p + 7, p + 5, p + 3), (p, p + 2, p + 4, p + 6)] - p += 8 - faces += [(p, p + 8, p + 9, p + 1), (p + 1, p + 9, p + 7, p + 3), (p + 3, p + 7, p + 5), - (p, p + 1, p + 3, p + 2), (p + 6, p + 7, p + 9, p + 8), (p + 4, p + 5, p + 7, p + 6), - (p + 2, p + 3, p + 5, p + 4), (p, p + 2, p + 6, p + 8), (p + 2, p + 4, p + 6)] - elif face_type == "neg_tri": - faces += [(p, p + 1, p + 7, p + 6), (p, p + 2, p + 3, p + 1), (p + 2, p + 4, p + 5, p + 3), - (p + 4, p + 6, p + 7, p + 5), (p + 1, p + 3, p + 5, p + 7), (p, p + 6, p + 4, p + 2)] - p += 8 - faces += [(p, p + 1, p + 9, p + 8), (p + 1, p + 3, p + 7, p + 9), (p + 3, p + 5, p + 7), - (p, p + 2, p + 3, p + 1), (p + 6, p + 8, p + 9, p + 7), (p + 4, p + 6, p + 7, p + 5), - (p + 2, p + 4, p + 5, p + 3), (p, p + 8, p + 6, p + 2), (p + 2, p + 6, p + 4)] - - return verts, faces - - -def stair_landing(w, depth, riser, orient): - hw, p = w / 2, 0 - if orient == "straight": - verts = [(-hw, 0.0, 0.0), (-hw, 0.0, riser), (-hw, depth, 0.0), (-hw, depth, riser), (hw, depth, 0.0), - (hw, depth, riser), (hw, 0.0, 0.0), (hw, 0.0, riser)] - elif orient == "left": - verts = [(-hw - w, 0.0, 0.0), (-hw - w, 0.0, riser), (-hw - w, depth, 0.0), (-hw - w, depth, riser), - (hw, depth, 0.0), (hw, depth, riser), (hw, 0.0, 0.0), (hw, 0.0, riser)] - else: - verts = [(-hw, 0.0, 0.0), (-hw, 0.0, riser), (-hw, depth, 0.0), (-hw, depth, riser), (hw + w, depth, 0.0), - (hw + w, depth, riser), (hw + w, 0.0, 0.0), (hw + w, 0.0, riser)] - - faces = [(p, p + 1, p + 3, p + 2), (p + 1, p + 7, p + 5, p + 3), (p + 4, p + 5, p + 7, p + 6), - (p, p + 2, p + 4, p + 6), (p + 2, p + 3, p + 5, p + 4), (p, p + 6, p + 7, p + 1)] - - return verts, faces - - -def normal_stairs(tread, riser, over_front, over_sides, w, ov, t_steps, is_close, set_in, is_riser, is_light, landing, - is_custom_tread): - verts, faces = [], [] - tread -= over_front - # figure number of jacks - if set_in: - jack_type = "set_in" - jack_num = 2 - elif is_close: - jack_num = int(w / 0.3048) - jack_type = "normal" - else: - jack_type = "normal" - jack_num = int(w / 0.3048) - - if is_light and jack_type == "normal" and jack_num % 2 != 0: - jack_num += 1 - - cx, t, cz, sy, cy = -(w / 2), 0.0508, 0, 0, 0 - space = (w - t) / (jack_num - 1) # current x, thickness, space between jacks - extra = 6 / METRIC_INCH - t_height, t_depth = t_steps * riser, t_steps * tread - I - - if jack_type == "set_in": - riser -= t - ty = (t * tread) / (riser + t) - ow, width = tread + tread + ty, tread + (2 * ty) - - for jack in range(jack_num): # each jack - if jack_type != "set_in": - cy = 0 if not is_riser else I - else: - cy = 0.0 - - # calculate line slope - if not is_close or (jack != 0 and jack != jack_num - 1): - point = [extra + cy, 0.0] - point_1 = [tread * t_steps + cy - I, (riser * t_steps) - extra] - slope = (point[1] - point_1[1]) / (point[0] - point_1[0]) - b = point[1] - (slope * point[0]) - elif (jack == 0 or jack == jack_num - 1) and is_close: - b, slope = 0.0, 0.0 - - last = 0.0 - if is_close and jack in (0, jack_num - 1): - last = t_height - extra - - cz, sy = riser, cy - for stair in range(t_steps): - face_type = "normal" - p = len(verts) - - if jack_type == "normal": - if stair == 0: - z = (slope * (cy + tread)) + b - verts += [(cx, cy, cz - riser), (cx + t, cy, cz - riser), (cx, cy, cz), (cx + t, cy, cz), - (cx, cy + tread, cz), (cx + t, cy + tread, cz), (cx, cy + tread, z), - (cx + t, cy + tread, z), (cx, cy + extra, cz - riser), (cx + t, cy + extra, cz - riser)] - face_type = "first" - elif stair == t_steps - 1: - verts += [(cx, cy, cz), (cx + t, cy, cz), (cx, cy + tread - I, cz), (cx + t, cy + tread - I, cz), - (cx, cy + tread - I, cz - extra - last), (cx + t, cy + tread - I, cz - extra - last)] - else: - z = (slope * (cy + tread)) + b - verts += [(cx, cy, cz), (cx + t, cy, cz), (cx, cy + tread, cz), (cx + t, cy + tread, cz), - (cx, cy + tread, z), (cx + t, cy + tread, z)] - - if face_type == "first": - faces += [(p, p + 1, p + 3, p + 2), (p + 2, p + 3, p + 5, p + 4), (p, p + 2, p + 4, p + 6), - (p + 1, p + 7, p + 5, p + 3), (p + 6, p + 7, p + 9, p + 8), (p, p + 8, p + 9, p + 1), - (p, p + 6, p + 8), (p + 1, p + 9, p + 7)] - else: - if stair == 1: - faces += [(p, p + 1, p + 3, p + 2), (p, p + 2, p + 4, p - 6), (p - 6, p + 4, p - 4), - (p, p - 6, p - 5, p + 1), (p + 1, p - 5, p + 5, p + 3), (p - 5, p - 3, p + 5), - (p - 3, p - 4, p + 4, p + 5)] - else: - faces += [(p, p + 1, p + 3, p + 2), (p, p + 2, p + 4, p - 4), (p - 4, p + 4, p - 2), - (p, p - 4, p - 3, p + 1), (p + 1, p - 3, p + 5, p + 3), (p - 3, p - 1, p + 5), - (p - 1, p - 2, p + 4, p + 5)] - if stair == t_steps - 1: - if t_steps == 1: - faces.append((p + 4, p + 5, p + 7, p + 6)) - else: - faces.append((p + 2, p + 3, p + 5, p + 4)) - - cy += tread - cz += riser - elif jack_type == "set_in": - if stair == 0: - cy = -tread if landing != 0 else 0 - cz = 0.0 - - if landing != 0: - verts += [(cx, cy + tread, cz), (cx + I, cy + tread, cz), (cx + t, cy + tread, cz), - (cx, cy + tread, cz + riser), (cx + I, cy + tread, cz + riser), - (cx + t, cy + tread, cz + riser)] - else: - verts += [(cx, cy, cz), (cx + I, cy, cz), (cx + t, cy, cz), (cx, cy + tread - ty, cz + riser), - (cx + I, cy + tread - ty, cz + riser), (cx + t, cy + tread - ty, cz + riser)] - - verts += [(cx, cy + tread, cz + riser + t), (cx + I, cy + tread, cz + riser + t), - (cx + t, cy + tread, cz + riser + t), (cx, cy + ow, cz + riser + t), - (cx + I, cy + ow, cz + riser + t), (cx + t, cy + ow, cz + riser + t), - (cx, cy + ow - ty, cz + riser), (cx + I, cy + ow - ty, cz + riser), - (cx + t, cy + ow - ty, cz + riser), (cx, cy + width - ty, cz), - (cx + I, cy + width - ty, cz), (cx + t, cy + width - ty, cz)] - cy += tread + tread - ty - cz += riser + riser + t - face_type = "first" if jack == 0 else "first_right" - elif stair == t_steps - 1: - verts += [(cx, cy, cz), (cx + I, cy, cz), (cx + t, cy, cz), (cx, cy + ty, cz + t), - (cx + I, cy + ty, cz + t), (cx + t, cy + ty, cz + t), (cx, cy + width - ty, cz + t), - (cx + I, cy + width - ty, cz + t), (cx + t, cy + width - ty, cz + t), - (cx, cy + tread + ty, cz), (cx + I, cy + tread + ty, cz), (cx + t, cy + tread + ty, cz)] - - cz += riser + t + t - cy += tread + ty - face_type = "last" if jack == 0 else "last_right" - verts += [(cx, cy, cz), (cx + I, cy, cz), (cx + t, cy, cz)] - else: - verts += [(cx, cy, cz), (cx + I, cy, cz), (cx + t, cy, cz), (cx, cy + ty, cz + t), - (cx + I, cy + ty, cz + t), (cx + t, cy + ty, cz + t), - (cx, cy + width, cz + t), (cx + I, cy + width, cz + t), (cx + t, cy + width, cz + t), - (cx, cy + tread + ty, cz), (cx + I, cy + tread + ty, cz), (cx + t, cy + tread + ty, cz)] - - cy += tread - cz += riser + t - if jack != 0: - face_type = "normal_right" - - if face_type == "first": - faces += [(p, p + 3, p + 12, p + 15), (p, p + 1, p + 4, p + 3), (p + 1, p + 2, p + 5, p + 4), - (p + 2, p + 17, p + 14, p + 5), (p + 16, p + 13, p + 14, p + 17), - (p + 15, p + 12, p + 13, p + 16), (p, p + 15, p + 16, p + 1), - (p + 1, p + 16, p + 17, p + 2), (p + 3, p + 4, p + 7, p + 6), - (p + 4, p + 5, p + 14, p + 13), (p + 4, p + 13, p + 10, p + 7), - (p + 12, p + 9, p + 10, p + 13), (p + 3, p + 6, p + 9, p + 12)] - - if t_steps != 1: - faces.append((p + 7, p + 10, p + 11, p + 8)) - else: - faces.append((p + 6, p + 7, p + 10, p + 9)) - elif face_type == "first_right": - faces += [(p, p + 3, p + 12, p + 15), (p, p + 1, p + 4, p + 3), (p + 1, p + 2, p + 5, p + 4), - (p + 2, p + 17, p + 14, p + 5), (p + 16, p + 13, p + 14, p + 17), - (p + 15, p + 12, p + 13, p + 16), (p + 4, p + 5, p + 8, p + 7), - (p + 5, p + 14, p + 11, p + 8), (p + 14, p + 13, p + 10, p + 11), - (p + 12, p + 3, p + 4, p + 13), (p + 4, p + 7, p + 10, p + 13), - (p, p + 15, p + 16, p + 1), (p + 1, p + 16, p + 17, p + 2)] - - if t_steps != 1: - faces.append((p + 6, p + 9, p + 10, p + 7)) - else: - faces.append((p + 7, p + 8, p + 11, p + 10)) - elif face_type == "normal": - if stair == 1: - faces += [(p, p - 12, p - 11, p + 1), (p + 1, p - 11, p - 10, p + 2), - (p + 2, p - 10, p - 7, p + 11), (p - 8, p + 10, p + 11, p - 7), - (p - 9, p + 9, p + 10, p - 8), (p, p + 9, p - 9, p - 12), (p, p + 1, p + 4, p + 3), - (p + 1, p + 2, p + 11, p + 10), (p + 1, p + 10, p + 7, p + 4), - (p + 4, p + 7, p + 8, p + 5), (p + 6, p + 7, p + 10, p + 9), (p, p + 3, p + 6, p + 9)] - else: - faces += [(p, p - 9, p - 8, p + 1), (p + 1, p - 8, p - 7, p + 2), (p - 7, p - 4, p + 11, p + 2), - (p - 4, p - 5, p + 10, p + 11), (p - 5, p - 6, p + 9, p + 10), - (p, p + 9, p - 6, p - 9), (p, p + 1, p + 4, p + 3), (p + 1, p + 2, p + 11, p + 10), - (p + 1, p + 10, p + 7, p + 4), (p + 4, p + 7, p + 8, p + 5), - (p + 6, p + 7, p + 10, p + 9), (p, p + 3, p + 6, p + 9)] - elif face_type == "normal_right": - if stair == 1: - faces += [(p, p - 12, p - 11, p + 1), (p + 1, p - 11, p - 10, p + 2), - (p + 2, p - 10, p - 7, p + 11), (p - 8, p + 10, p + 11, p - 7), - (p - 9, p + 9, p + 10, p - 8), (p, p + 9, p - 9, p - 12), - (p + 1, p + 2, p + 5, p + 4), (p + 2, p + 11, p + 8, p + 5), - (p + 7, p + 8, p + 11, p + 10), (p, p + 1, p + 10, p + 9), - (p + 1, p + 4, p + 7, p + 10), (p + 3, p + 6, p + 7, p + 4)] - else: - faces += [(p, p - 9, p - 8, p + 1), (p + 1, p - 8, p - 7, p + 2), (p - 7, p - 4, p + 11, p + 2), - (p - 4, p - 5, p + 10, p + 11), (p - 5, p - 6, p + 9, p + 10), - (p, p + 9, p - 6, p - 9), (p + 1, p + 2, p + 5, p + 4), (p + 2, p + 11, p + 8, p + 5), - (p + 7, p + 8, p + 11, p + 10), (p, p + 1, p + 10, p + 9), - (p + 1, p + 4, p + 7, p + 10), (p + 3, p + 6, p + 7, p + 4)] - elif face_type == "last": - faces += [(p, p - 9, p - 8, p + 1), (p + 1, p - 8, p - 7, p + 2), (p - 7, p - 4, p + 11, p + 2), - (p - 4, p - 5, p + 10, p + 11), (p - 5, p - 6, p + 9, p + 10), (p, p + 9, p - 6, p - 9), - (p, p + 1, p + 4, p + 3), (p + 1, p + 2, p + 11, p + 10), (p + 1, p + 10, p + 7, p + 4), - (p + 4, p + 7, p + 8, p + 5), (p + 6, p + 7, p + 10, p + 9), (p, p + 3, p + 6, p + 9)] - elif face_type == "last_right": - faces += [(p, p - 9, p - 8, p + 1), (p + 1, p - 8, p - 7, p + 2), (p - 7, p - 4, p + 11, p + 2), - (p - 4, p - 5, p + 10, p + 11), (p - 5, p - 6, p + 9, p + 10), (p, p + 9, p - 6, p - 9), - (p + 1, p + 2, p + 5, p + 4), (p + 2, p + 11, p + 8, p + 5), - (p + 7, p + 8, p + 11, p + 10), (p, p + 1, p + 10, p + 9), (p + 1, p + 4, p + 7, p + 10), - (p + 3, p + 6, p + 7, p + 4)] - - if face_type in ("last_right", "last"): - faces += [(p + 3, p + 4, p + 13, p + 12), (p + 4, p + 5, p + 14, p + 13), (p + 5, p + 8, p + 14), - (p + 7, p + 13, p + 14, p + 8), (p + 6, p + 12, p + 13, p + 7), (p + 3, p + 12, p + 6)] - - cx += space - - verts_jacks, faces_jacks = verts, faces - verts, faces, custom_tread_pos = [], [], [] - ry = cy - cx, cy, cz, hw = 0.0, sy, 0.0, w / 2 - - if jack_type == "normal": - if ov == "4": - left = hw + over_sides - right = hw + over_sides - elif ov == "2": - left = hw - right = hw + over_sides - elif ov == "3": - left = hw + over_sides - right = hw - else: - left = hw - right = hw - - front = over_front + I if is_riser else over_front - - for step in range(t_steps): - e = 0 if not is_riser else I - p = len(verts) - - if is_riser: - verts += [(cx - hw, cy, cz), (cx - hw, cy - I, cz), (cx - hw, cy, cz + riser), - (cx - hw, cy - I, cz + riser), (cx + hw, cy, cz + riser), (cx + hw, cy - I, cz + riser), - (cx + hw, cy, cz), (cx + hw, cy - I, cz)] - - faces += [(p, p + 1, p + 3, p + 2), (p + 1, p + 7, p + 5, p + 3), (p + 4, p + 5, p + 7, p + 6), - (p, p + 2, p + 4, p + 6), (p + 2, p + 3, p + 5, p + 4), (p, p + 6, p + 7, p + 1)] - p += 8 - - cz += riser - - if not is_custom_tread: - verts += [(cx - left, cy - front, cz), (cx - left, cy - front, cz + I), (cx - left, cy + tread - e, cz), - (cx - left, cy + tread - e, cz + I), (cx + right, cy + tread - e, cz), - (cx + right, cy + tread - e, cz + I), (cx + right, cy - front, cz), - (cx + right, cy - front, cz + I)] - - faces += [(p, p + 1, p + 3, p + 2), (p + 1, p + 7, p + 5, p + 3), (p + 4, p + 5, p + 7, p + 6), - (p, p + 2, p + 4, p + 6), (p + 2, p + 3, p + 5, p + 4), (p, p + 6, p + 7, p + 1)] - else: - custom_tread_pos.append([cx, cy + tread - e, cz]) - cy += tread - ry -= I - elif jack_type == "set_in": - hw -= I - cz += riser - cy += tread if landing == 0 else 0 - - for step in range(t_steps): - p = len(verts) - verts += [(cx - hw, cy, cz), (cx - hw, cy, cz + t), (cx - hw, cy + tread, cz), - (cx - hw, cy + tread, cz + t), (cx + hw, cy + tread, cz), (cx + hw, cy + tread, cz + t), - (cx + hw, cy, cz), (cx + hw, cy, cz + t)] - - faces += [(p, p + 1, p + 3, p + 2), (p + 1, p + 7, p + 5, p + 3), (p + 4, p + 5, p + 7, p + 6), - (p, p + 2, p + 4, p + 6), (p + 2, p + 3, p + 5, p + 4), (p, p + 6, p + 7, p + 1)] - - cz += t + riser - cy += tread - t_height += t * t_steps + t - I - - return verts, faces, 0.0, ry, t_height + riser, verts_jacks, faces_jacks, custom_tread_pos - - -def update_stairs(self, context): - o = context.object - - mats = [] - for i in o.data.materials: - mats.append(i.name) - - verts, faces, names = create_stairs(self, context, o.jv_stair_style, o.jv_overhang_style, o.jv_num_steps, - o.jv_tread_width, o.jv_riser_height, o.jv_over_front, o.jv_over_sides, - o.jv_stair_width, o.jv_num_landings, o.jv_is_close_sides, o.jv_tread_width0, - o.jv_riser_height0, o.jv_landing_depth0, o.jv_landing_rot0, o.jv_over_front0, - o.jv_over_sides0, o.jv_overhang_style0, o.jv_is_backwards0, o.jv_landing_rot1, - o.jv_tread_width1, o.jv_riser_height1, o.jv_landing_depth1, o.jv_over_front1, - o.jv_over_sides1, o.jv_overhang_style1, o.jv_is_backwards1, o.jv_winding_rot, - o.jv_step_begin_rot, o.jv_spiral_rot, o.jv_num_steps0, o.jv_num_steps1, - o.jv_set_steps_in, o.jv_is_riser, o.jv_is_landing, o.jv_is_light, - o.jv_num_steps2, o.jv_tread_res, o.jv_pole_dia, o.jv_pole_res, - o.jv_is_custom_tread, o.jv_custom_treads) - - emesh = o.data - mesh = bpy.data.meshes.new(name="siding") - mesh.from_pydata(verts, [], faces) - mesh.update(calc_edges=True) - - for i in bpy.data.objects: - if i.data == emesh: - i.data = mesh - - emesh.user_clear() - bpy.data.meshes.remove(emesh) - - if context.scene.render.engine == "CYCLES": - if len(mats) >= 1: - o.data.materials.append(bpy.data.materials[mats[0]]) - else: - mat = bpy.data.materials.new("stairs_temp") - mat.use_nodes = True - o.data.materials.append(mat) - - if names: - for name in names: - ob = bpy.data.objects[name].select = True - o.select = True - context.scene.objects.active = o - bpy.ops.object.join() - - if o.jv_is_unwrap: - unwrap_object(self, context) - if o.jv_is_random_uv: - random_uvs(self, context) - - for i in mats: - if i not in o.data.materials: - mat = bpy.data.materials[i] - o.data.materials.append(mat) - - for i in bpy.data.materials: - if i.users == 0: - bpy.data.materials.remove(i) - - -def stair_materials(self, context): - o = context.object - if context.scene.render.engine == "CYCLES": - if o.jv_col_image == "" or (o.jv_norm_image == "" and o.jv_is_bump): - self.report({"ERROR"}, "JARCH Vis: Image Filepaths Are Invalid") - return - - mat = image_material(bpy, o.jv_im_scale, o.jv_col_image, o.jv_norm_image, o.jv_bump_amo, o.jv_is_bump, - "stairs_temp_" + o.name, True, 0.1, 0.05, o.jv_is_rotate, None) - if mat is not None: - mat.name = "stairs_" + o.name - if len(o.data.materials) >= 1: - o.data.materials[0] = mat - else: - o.data.materials.append(mat) - else: - self.report({"ERROR"}, "Images Not Found, Make Sure Path Is Correct") - - if o.jv_stair_style in ("1", "3"): - if o.jv_col_image2 == "" or (o.jv_norm_image2 == "" and o.jv_is_bump2): - self.report({"ERROR"}, "JARCH Vis: Image Filepaths Are Invalid") - return - - mat = image_material(bpy, o.jv_im_scale2, o.jv_col_image2, o.jv_norm_image2, o.jv_bump_amo2, o.jv_is_bump2, - "second_temp_" + o.name, True, 0.1, 0.05, o.jv_is_rotate2, None) - if mat is not None: - mat.name = "jacks_" + o.name if o.jv_stair_style == "1" else "pole_" + o.name - if len(o.data.materials) >= 2: - o.data.materials[1] = mat - else: - o.data.materials.append(mat) - else: - self.report({"ERROR"}, "Images Not Found, Make Sure Path Is Correct") - - for i in bpy.data.materials: - if i.users == 0: - bpy.data.materials.remove(i) - else: - self.report({"ERROR"}, "JARCH VIS: Render Engine Must Be Cycles To Create Materials") - - -class StairsPanel(bpy.types.Panel): - bl_idname = "OBJECT_PT_jarch_stairs" - bl_label = "JARCH Vis: Stairs" - bl_space_type = "VIEW_3D" - bl_region_type = "TOOLS" - bl_category = "JARCH Vis" - - def draw(self, context): - layout = self.layout - o = context.object - if bpy.context.mode == "EDIT_MESH": - layout.label("JARCH Vis: Stairs Doesn't Work In Edit Mode", icon="ERROR") - else: - if o is not None: - if o.jv_internal_type == "stair": - if o.jv_object_add == "add": - layout.label("Style:", icon="OBJECT_DATA") - layout.prop(o, "jv_stair_style") - layout.separator() - layout.prop(o, "jv_stair_width") - layout.separator() - - if o.jv_stair_style != "3": - layout.prop(o, "jv_num_steps") - else: - layout.prop(o, "jv_num_steps2") - - if o.jv_stair_style != "3": - layout.prop(o, "jv_tread_width") - - layout.prop(o, "jv_riser_height") - h = round((o.jv_num_steps * o.jv_riser_height) + (1 / METRIC_INCH), 2) - if context.scene.unit_settings.system == "IMPERIAL": - layout.label("Height: " + str(round(((h * METRIC_INCH) / 12), 2)) + " ft", icon="INFO") - layout.separator() - else: - layout.label("Height: " + str(round(h, 2)) + " m", icon="INFO") - layout.separator() - - if o.jv_stair_style == "1": - layout.prop(o, "jv_set_steps_in", icon="OOPS") - - if not o.jv_set_steps_in: - layout.separator() - layout.prop(o, "jv_is_custom_tread", icon="OBJECT_DATAMODE") - if o.jv_is_custom_tread: - layout.prop_search(o, "jv_custom_treads", context.scene, "objects") - layout.separator() - - if not o.jv_set_steps_in: - layout.prop(o, "jv_is_close_sides", icon="AUTOMERGE_ON") - layout.prop(o, "jv_is_light", icon="OUTLINER_OB_LAMP") - if not o.jv_set_steps_in and o.jv_stair_style != "3": - layout.separator() - if o.jv_stair_style == "1": - layout.label("Overhang Style:", icon="OUTLINER_OB_SURFACE") - layout.prop(o, "jv_overhang_style") - layout.prop(o, "jv_over_front") - - if o.jv_overhang_style != "1": - layout.prop(o, "jv_over_sides") - if o.jv_stair_style == "1": - layout.separator() - layout.prop(o, "jv_is_riser", icon="TRIA_UP") - layout.separator() - else: - layout.prop(o, "jv_over_front") - layout.separator() - - if o.jv_stair_style == "1": # normal stairs - layout.separator() - layout.prop(o, "jv_num_landings") - if o.jv_num_landings > 0: - layout.prop(o, "jv_is_landing", icon="FULLSCREEN") - - for i in range(int(o.jv_num_landings)): - layout.separator() - layout.separator() - box = layout.box() - box.label("Stair Set " + str(i + 2) + ":", icon="MOD_ARRAY") - box.separator() - box.prop(o, "jv_num_steps" + str(i)) - - if o.jv_is_custom_tread: - box.prop(o, "jv_tread_width" + str(i)) - box.prop(o, "jv_riser_height" + str(i)) - - if i == 0: - h2 = h + round((o.jv_riser_height0 * o.jv_num_steps0) + o.jv_riser_height + - (1 / METRIC_INCH), 2) - if context.scene.unit_settings.system == "IMPERIAL": - box.label("Height: " + str(round(((h2 * METRIC_INCH) / 12), 2)) + " ft", - icon="INFO") - else: - box.label("Height: " + str(round(h2, 2)) + " m", icon="INFO") - else: - h2 = h + round((o.jv_riser_height0 * o.jv_num_steps0) + - (o.jv_riser_height0 * o.jv_num_steps0) + (2 / METRIC_INCH) + - o.jv_riser_height + o.jv_riser_height0, 2) - if context.scene.unit_settings.system == "IMPERIAL": - box.label("Height: " + str(round(((h2 * METRIC_INCH) / 12), 2)) + " ft", - icon="INFO") - else: - box.label("Height: " + str(round(h2, 2)) + " m", icon="INFO") - box.separator() - box.label("Landing " + str(i + 1) + " Rotation:") - box.prop(o, "jv_landing_rot" + str(i)) - - if (i == 0 and o.jv_landing_rot0 != "1") or (i == 1 and o.jv_landing_rot1 != "1"): - box.prop(o, "jv_is_backwards" + str(i), icon="LOOP_BACK") - box.prop(o, "jv_landing_depth" + str(i)) - - if not o.jv_set_steps_in: - box.separator() - box.label("Overhang Style:", icon="OUTLINER_OB_SURFACE") - box.prop(o, "jv_overhang_style" + str(i)) - box.prop(o, "jv_over_front" + str(i)) - if (i == 0 and o.jv_overhang_style0 != "1") or \ - (i == 1 and o.jv_overhang_style1 != "1"): - box.prop(o, "jv_over_sides" + str(i)) - elif o.jv_stair_style == "2": - layout.prop(o, "jv_step_begin_rot") - row = self.layout.row() - row.label("Rotation: ") - row.prop(o, "jv_winding_rot") - elif o.jv_stair_style == "3": - layout.prop(o, "jv_spiral_rot", icon="MAN_ROT") - layout.prop(o, "jv_tread_res") - layout.separator() - layout.prop(o, "jv_pole_dia") - layout.prop(o, "jv_pole_res") - - layout.separator() - layout.prop(o, "jv_is_unwrap", icon="GROUP_UVS") - if o.jv_is_unwrap: - layout.prop(o, "jv_is_random_uv", icon="RNDCURVE") - layout.separator() - - if context.scene.render.engine == "CYCLES": - layout.prop(o, "jv_is_material", icon="MATERIAL") - else: - layout.label("Materials Only Supported With Cycles", icon="POTATO") - layout.separator() - - if o.jv_is_material and context.scene.render.engine == "CYCLES": - box = layout.box() - box.label("Stairs:") - box.prop(o, "jv_col_image", icon="COLOR") - box.prop(o, "jv_is_bump", icon="SMOOTHCURVE") - box.separator() - - if o.jv_is_bump: - box.prop(o, "jv_norm_image", icon="TEXTURE") - box.prop(o, "jv_bump_amo") - box.prop(o, "jv_im_scale", icon="MAN_SCALE") - box.prop(o, "jv_is_rotate", icon="MAN_ROT") - - layout.separator() - if o.jv_stair_style in ("1", "3"): - box = layout.box() - if o.jv_stair_style == "1": - box.label("Jacks:") - else: - box.label("Pole:") - box.prop(o, "jv_col_image2", icon="COLOR") - box.prop(o, "jv_is_bump2", icon="SMOOTHCURVE") - box.separator() - - if o.jv_is_bump2: - box.prop(o, "jv_norm_image2", icon="TEXTURE") - box.prop(o, "jv_bump_amo2") - box.prop(o, "jv_im_scale2", icon="MAN_SCALE") - box.prop(o, "jv_is_rotate2", icon="MAN_ROT") - layout.separator() - layout.operator("mesh.jv_stairs_materials", icon="MATERIAL") - - layout.separator() - layout.separator() - layout.operator("mesh.jv_stairs_update", icon="FILE_REFRESH") - layout.operator("mesh.jv_stairs_delete", icon="CANCEL") - layout.operator("mesh.jv_stairs_add", icon="MOD_ARRAY") - else: - layout.operator("mesh.jv_stairs_add", icon="MOD_ARRAY") - else: - if o.jv_internal_type != "": - layout.label("This Is Already A JARCH Vis Object", icon="INFO") - layout.operator("mesh.jv_stairs_add", icon="MOD_ARRAY") - else: - layout.operator("mesh.jv_stairs_add", icon="MOD_ARRAY") - - -class StairsAdd(bpy.types.Operator): - bl_idname = "mesh.jv_stairs_add" - bl_label = "Add Stairs" - bl_description = "JARCH Vis: Stair Generator" - bl_options = {"UNDO"} - - @classmethod - def poll(cls, context): - return context.mode == "OBJECT" - - def execute(self, context): - bpy.ops.mesh.primitive_cube_add() - o = context.object - o.jv_internal_type = "stair" - o.jv_object_add = "add" - return {"FINISHED"} - - -class StairsDelete(bpy.types.Operator): - bl_idname = "mesh.jv_stairs_delete" - bl_label = "Delete Stairs" - bl_options = {"UNDO", "INTERNAL"} - - def execute(self, context): - bpy.ops.object.delete() - for i in bpy.data.materials: - if i.users == 0: - bpy.data.materials.remove(i) - return {"FINISHED"} - - -class StairsUpdate(bpy.types.Operator): - bl_idname = "mesh.jv_stairs_update" - bl_label = "Update Stairs" - bl_options = {"UNDO", "INTERNAL"} - - def execute(self, context): - update_stairs(self, context) - return {"FINISHED"} - - -class StairsMaterial(bpy.types.Operator): - bl_idname = "mesh.jv_stairs_materials" - bl_label = "Generate\\Update Materials" - bl_options = {"UNDO", "INTERNAL"} - - def execute(self, context): - stair_materials(self, context) - return {"FINISHED"} - - -class StairsMesh(bpy.types.Operator): - bl_idname = "mesh.jarch_stairs_mesh" - bl_label = "Convert To Mesh" - bl_description = "Converts Stair Object To Normal Object (No Longer Editable)" - bl_options = {"UNDO", "INTERNAL"} - - def execute(self, context): - o = context.object - o.jv_object_add = "mesh" - return {"FINISHED"} - - -def register(): - bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/jv_types.py b/jv_types.py new file mode 100644 index 0000000..198090d --- /dev/null +++ b/jv_types.py @@ -0,0 +1,29 @@ +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from . jv_flooring import JVFlooring +from . jv_siding import JVSiding +from . jv_roofing import JVRoofing +from . jv_windows import JVWindows + + +registered_types = { + "flooring": JVFlooring, + "siding": JVSiding, + "roofing": JVRoofing, + "windows": JVWindows +} + + +def get_object_type_handler(object_type: str): + return registered_types.get(object_type, None) diff --git a/jv_utils.py b/jv_utils.py index f04258f..a549b6d 100644 --- a/jv_utils.py +++ b/jv_utils.py @@ -1,290 +1,152 @@ -# This file is part of JARCH Vis -# -# JARCH Vis is free software: you can redistribute it and/or modify +# This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # -# JARCH Vis is distributed in the hope that it will be useful, +# This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with JARCH Vis. If not, see . - -# constants -METRIC_INCH = 39.3701 -METRIC_FOOT = 3.28084 -I = 1 / METRIC_INCH -HI = 0.5 / METRIC_INCH - - -def delete_materials(self, context): - import bpy - o = context.object - if not o.jv_is_material: - for i in o.data.materials: - bpy.ops.object.material_slot_remove() - - for i in bpy.data.materials: - if i.users == 0: - bpy.data.materials.remove(i) - - -def preview_materials(self, context): - import bpy - for area in bpy.context.screen.areas: - if area.type == 'VIEW_3D': - for space in area.spaces: - if space.type == 'VIEW_3D': - if bpy.context.object.jv_is_preview: - space.viewport_shade = 'RENDERED' - else: - space.viewport_shade = "SOLID" - - -def unwrap_object(self, context): - import bpy - - o = context.object - for ob in context.selected_objects: - ob.select = False - o.select = True - context.scene.objects.active = o - - bpy.ops.object.editmode_toggle() - bpy.ops.mesh.select_all(action="SELECT") - bpy.ops.uv.cube_project() - bpy.ops.object.editmode_toggle() - - -def random_uvs(self, context): - import bpy - import bmesh - from mathutils import Vector - from random import uniform - bpy.ops.object.editmode_toggle() - bpy.ops.mesh.select_all(action="SELECT") - obj = bpy.context.object - me = obj.data - bm = bmesh.from_edit_mesh(me) - - uv_layer = bm.loops.layers.uv.verify() - bm.faces.layers.tex.verify() - # adjust UVs - for f in bm.faces: - offset = Vector((uniform(-1.0, 1.0), uniform(-1.0, 1.0))) - for v in f.loops: - luv = v[uv_layer] - luv.uv = (luv.uv + offset).xy - - bmesh.update_edit_mesh(me) - bpy.ops.object.editmode_toggle() - - -def update_roofing_facegroup_selection(self, context): - import bpy - # updates which faces are selected based on which face group is selected in the UI## - ob = context.object - bpy.ops.object.editmode_toggle() - - if len(ob.jv_face_groups) >= 1: - fg = ob.jv_face_groups[ob.jv_face_group_index] - - if "+" in fg.data: # older version - while len(ob.jv_face_groups): - ob.jv_face_groups.remove(len(ob.jv_face_groups) - 1) - ob.jv_face_group_index = 0 - ob.jv_face_group_ct = 0 +# along with this program. If not, see . + +from mathutils import Vector, Euler +from typing import List, Tuple +from bpy.types import MeshPolygon, MeshVertex +from math import atan, radians, acos + + +class Units: + FOOT: float = 1 / 3.248 + INCH: float = FOOT / 12 + TQ_INCH: float = INCH * 0.75 # 3/4 inch + H_INCH: float = INCH / 2 # 1/2 inch + Q_INCH: float = H_INCH / 2 # 1/4 inch + ETH_INCH: float = INCH / 8 # 1/8th inch + STH_INCH: float = INCH / 16 # 1/16th inch + + +class CuboidalRegion: + """ + A representation of a cube-shaped region defined by six planes. Can be used to tell if a point is contained + within the cube. + """ + def __init__(self, planes: List[Tuple[tuple, tuple]]): + """ + Take a list of the defining planes. Each plane is defined by a point on that plane and its normal. + All plane normals should point towards the center of the cube. + :param planes: tuples of (point on plane, plane normal) + """ + self.planes = [(Vector(po), Vector(no)) for po, no in planes] # convert to vectors for easier math later + + def __contains__(self, item): + """ + Determine if the item/point is in the cube by determining if it is on the positive sides of all the planes using + the dot product. + :param item: a Vector to check whether or not it is in the plane + :return: a boolean indicating whether the point is in the cube or not + """ + for pos, normal in self.planes: + if normal.dot(item - pos) < 0: + return False else: - # deselect all faces and edges - for f in ob.data.polygons: - f.select = False - for e in ob.data.edges: - e.select = False - for v in ob.data.vertices: - v.select = False - - temp_l = [] - for i in fg.data.split(","): - if int(i) < len(ob.data.polygons): - temp_l.append(round_tuple(tuple(ob.data.polygons[int(i)].center), 4)) - - # select correct faces - for f in temp_l: - for face_in_obj in ob.data.polygons: - if round_tuple(tuple(face_in_obj.center), 4) == f: - face_in_obj.select = True - - # make sure selection list is up to date - bpy.ops.object.editmode_toggle() - bpy.ops.object.editmode_toggle() - - bpy.ops.object.editmode_toggle() - - -# convert -def convert(num, to): - if to == "ft": - out = num / 3.28084 - else: - out = num / 39.3701 - re = round(out, 5) - return re - - -# point rotation -def point_rotation(data, origin, rot): - from math import sin, cos - # Takes in a single tuple (x, y) or a tuple of tuples ((x, y), (x, y)) and rotates them rot or [rot1, rot2] radians - # around origin where rot1, rot2 match up to tuples in data so each point can have a separate rotation, - # returns new (x, y) or ((x, y), (x, y)) values### - data_list = [] - if isinstance(data[0], float) or isinstance(data[0], int): - data_list.append(data) - else: - data_list = data - - if isinstance(rot, float) or isinstance(rot, int): - rot_list = [rot] * len(data_list) - else: - rot_list = rot - - out_list = [] - ct = 0 - for point in data_list: - new_point = (point[0] - origin[0], point[1] - origin[1]) - x = new_point[0] - y = new_point[1] - cur_rot = rot_list[ct] - new_x = (x * cos(cur_rot)) - (y * sin(cur_rot)) - new_y = (x * sin(cur_rot)) - (y * cos(cur_rot)) - out = (new_x + origin[0], new_y + origin[1]) - out_list.append((out[0], out[1])) - ct += 1 - - if isinstance(data[0], float) or isinstance(data[0], int): - return out_list[0] - else: - return out_list - - -# find objects rotation -def object_rotation(obj): - from math import atan, radians - # get list of vertices - verts = [(obj.matrix_world * i.co).to_tuple() for i in obj.data.vertices] - # create temporary list then round any numbers that are really small to zero - v1_temp = verts[0] - v1 = [] - for num in v1_temp: - if -0.00001 < num < 0.00001: - num = 0.0 - v1.append(round(num, 5)) - # find numbers that work with the original number - v2_temp = 0 - for i in verts: - i2 = [] - for num in i: # round off numbers if close - if -0.00001 < num < 0.00001: - num = 0.0 - i2.append(round(num, 5)) - # are number correct? - if (i2[0] != v1[0] and i2[1] == v1[1]) or (i2[0] == v1[0] and i2[1] != v1[1]) or (i2[0] != v1[0] and i2[1] != v1[1]) and v2_temp == 0: - v2_temp = i2 - # calculate angle - if v2_temp != 0: - skip = True if v2_temp[0] != v1[0] and v2_temp[1] == v1[1] else False # plane is inline with x-axis - - if not skip: - v2 = (v2_temp[0] - v1[0], v2_temp[1] - v1[1], v2_temp[2] - v1[2]) - if v2[0] != 0: - angle = atan(v2[1] / v2[0]) - else: - angle = radians(90) + return True + + +def determine_face_group_scale_rot_loc(faces: List[MeshPolygon], vertices: List[MeshVertex], fg): + """ + Determine the rotation of the faces from a plane lying in the X-Y plane with normal (0, 0, 1). + Rotate the face points into the X-Y plane using that rotation and then determine the offset of the + bottom-left corner from the origin and the X-Y dimensions of the faces + :param faces: a list of bpy.types.MeshPolygons that make up the face group + :param vertices: a list of bpy.types.MeshVertexs that make up the face group + :param fg: the face group to assign the rot, loc, and dim values to + """ + # ROTATION - the amount of rotation needed to get a point in X-Y plane to the face + normal = Vector(faces[0].normal) + + # determine how much you have to rotate to go from the +Z axis to the normal vector + # theta = rotation on x-axis. atan is defined from (-90, 90), modify rotation to be from +Y axis not +X + if normal[0] != 0: + theta = atan(normal[1] / normal[0]) + if normal[0] < 0: + theta -= radians(90) else: - angle = 0 - else: - angle = radians(90) - - return angle - - -# TODO: fix to account for rotation on x-axis -# figure exact object dimensions -def object_dimensions(obj): - from math import sqrt - - verts = [(obj.matrix_world * i.co).to_tuple() for i in obj.data.vertices] - sx, bx, sy, by, sz, bz = 0, 0, 0, 0, 0, 0 - - for val in verts: - # x axis - if val[0] < sx: - sx = val[0] - elif val[0] > bx: - bx = val[0] - # y axis - if val[1] < sy: - sy = val[1] - elif val[1] > by: - by = val[1] - # z axis - if val[2] < sz: - sz = val[2] - elif val[2] > bz: - bz = val[2] - # lengths - x = (round(bx - sx, 5)) * obj.scale[0] - y = (round(by - sy, 5)) * obj.scale[1] - z = (round(bz - sz, 5)) * obj.scale[2] - - dia = sqrt(x ** 2 + y ** 2) - - return [dia, z] + theta += radians(90) - -def round_tuple(tup, digits): - temp = [] - for i in tup: - temp.append(round(i, digits)) - return tuple(temp) - - -def rot_from_normal(normal): - from math import radians, atan, acos - theta = radians(90) - if normal[0]: - theta = abs(atan(normal[1] / normal[0])) - - if normal[0] <= 0: + else: # either on +Y or -Y axis if normal[1] > 0: - theta = radians(180) - theta + theta = radians(180) else: - theta += radians(180) - elif normal[1] < 0: - theta *= -1 - - phi = acos(normal[2]) - - return 0, phi, theta - - -# adds all values from v into l -def append_all(original_list: list, values: list): - for i in values: - original_list.append(i) - - -def apply_modifier_boolean(bpy, ob, bool_name: str): - bpy.ops.object.modifier_add(type="BOOLEAN") - pos = len(ob.modifiers) - 1 - bpy.context.object.modifiers[pos].object = bpy.data.objects[bool_name] - bpy.context.object.modifiers[pos].solver = "CARVE" - bpy.ops.object.modifier_apply(apply_as="DATA", modifier=ob.modifiers[0].name) - - -def round_rad(deg: float, r=4) -> float: - from math import radians - return round(radians(deg), r) + theta = 0 + + rho = acos(normal[2]) + rot = Euler((rho, 0, theta)) + + # determine how much you have to go to rotate all the points into the X-Y plane + # do z rotation before x rotation to ensure everything ends up properly + inv_rot1 = Euler((0, 0, -theta)) + inv_rot2 = Euler((-rho, 0, 0)) + + # rotate all points into the X-Y plane + vertices = [Vector(v.co) for v in vertices] + for v in vertices: + v.rotate(inv_rot1) + v.rotate(inv_rot2) + + # DIMENSIONS and LOCATION + min_x, min_y, min_z, max_x, max_y, max_z = *vertices[0], *vertices[0] + for vert in vertices: + min_x = min(min_x, vert[0]) + max_x = max(max_x, vert[0]) + + min_y = min(min_y, vert[1]) + max_y = max(max_y, vert[1]) + + min_z = min(min_z, vert[2]) + max_z = max(max_z, vert[2]) + + # ultimately ignore z as the points have been rotated into the X-Y plane + fg.rotation = rot + fg.dimensions = (max_x-min_x, max_y-min_y) + + loc = Vector((min_x, min_y, min_z)) + loc.rotate(Euler((rho, 0, 0))) + loc.rotate(Euler((0, 0, theta))) + + fg.location = loc + + +def determine_bisecting_planes(edges: set, vertices: set, fg, normal: Vector): + """ + Calculate vectors that are in the plane of the face group, perpendicular to the edge, and pointer inward + :param edges: the set of boundary edges for the face group (bmesh.types.BMEdge) + :param vertices: all vertices in the face group (bpy.types.MeshVertex) + :param fg: the face group itself + :param normal: the normal of the faces in the face group + """ + face_group_center = Vector((0, 0, 0)) + for vertex in vertices: + face_group_center += Vector(vertex.co) + face_group_center /= len(vertices) + + for edge in edges: + v1, v2, = edge.verts[0].co, edge.verts[1].co + edge_v = Vector((v2[0] - v1[0], v2[1] - v1[1], v2[2] - v1[2])) + + bisecting_plane = fg.bisecting_planes.add() + edge_center = Vector(((v2[0]+v1[0]) / 2, (v2[1]+v1[1]) / 2, (v2[2]+v1[2]) / 2)) + bisecting_plane.center = edge_center + + # the cross product of the edge and the normal of the face will be perpendicular to both and in the plane + edge_normal = edge_v.cross(normal) + edge_normal_neg = edge_normal.copy() + edge_normal_neg.negate() + + # see if the edge_normal or it's inverse is closer to the center of the faces. Pick the one closer + discerner = Vector(bisecting_plane.center) - face_group_center + normal + if edge_normal.angle(discerner) < edge_normal_neg.angle(discerner): + edge_normal = edge_normal_neg + + bisecting_plane.normal = edge_normal diff --git a/jv_windows.py b/jv_windows.py index d7b9355..00e7c3a 100644 --- a/jv_windows.py +++ b/jv_windows.py @@ -1,1077 +1,854 @@ -# This file is part of JARCH Vis -# -# JARCH Vis is free software: you can redistribute it and/or modify +# This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # -# JARCH Vis is distributed in the hope that it will be useful, +# This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with JARCH Vis. If not, see . - -import bpy -from math import radians, sqrt, acos, cos, sin, asin -from . jv_utils import METRIC_INCH, I, HI, point_rotation, random_uvs, unwrap_object -from . jv_materials import glossy_diffuse_material, image_material, architectural_glass_material -from mathutils import Vector, Matrix -# import jv_properties - -# variables for pane size -t = 1.5 / METRIC_INCH -bevelY = HI -bevelXZ = 0.25 / METRIC_INCH -ei = 0.125 / METRIC_INCH -y = -t / 2 - - -# an iterator that returns x,y coordinates for ellipse based on resolution -def arch_function(half_width, half_height, res): - step = (2*half_width) / res - for i in range(res - 1): - x = step * i - half_width + step - y = (half_height/half_width) * sqrt((half_width**2) - (x**2)) - - yield (x, y) - - -# roundness refers to what percentage of height that arch is -def arch(width, height, roundness, res, is_slider, jamb_w): - verts, faces, glass_indices = [], [], [] - - arch_h = height * (1 / (100 / roundness)) - arch_h2 = arch_h + I - h = height - arch_h # height to bottom of arch - hw = width / 2 - hw2 = width / 2 + I - w = jamb_w / 2 - y = -t / 2 - - # jamb base - verts += [(-hw - I, -w, 0.0), (hw + I, -w, 0.0), (hw + I, w, 0.0), (-hw - I, w, 0.0), (-hw, -w, I), (hw, -w, I), - (hw, w, I), (-hw, w, I), (-hw - I, -w, h + I), (-hw, -w, h + I), (-hw, w, h + I), (-hw - I, w, h + I), - (hw, -w, h + I), (hw + I, -w, h + I), (hw + I, w, h + I), (hw, w, h + I)] - - faces += [(0, 3, 2, 1), (0, 1, 5, 4), (2, 3, 7, 6), (4, 5, 6, 7), (0, 4, 9, 8), (0, 8, 11, 3), (3, 11, 10, 7), - (4, 7, 10, 9), (1, 2, 14, 13), (1, 13, 12, 5), (5, 12, 15, 6), (2, 6, 15, 14)] - - p = len(verts) - p2 = len(verts) - - # jamb arch - for i in arch_function(hw, arch_h, res): - verts += [(i[0], -w, i[1] + h + I), (i[0], w, i[1] + h + I)] - - off = len(verts) - p - for i in arch_function(hw2, arch_h2, res): - verts += [(i[0], -w, i[1] + h + I), (i[0], w, i[1] + h + I)] - - # jamb faces - for i in range(res - 2): # arch faces - faces += [(p, p + 1, p + 3, p + 2), (p, p + 2, p + off + 2, p + off), - (p + off, p + off + 2, p + off + 3, p + off + 1), (p + 1, p + off + 1, p + off + 3, p + 3)] - p += 2 - - # connecting faces - faces += [(p2 - 8, p2 - 7, p2, p2 + off), (p2 - 7, p2 - 6, p2 + 1, p2), (p2 - 6, p2 - 5, p2 + off + 1, p2 + 1), - (p2 - 8, p2 + off, p2 + off + 1, p2 - 5), (p2 - 4, p2 - 3, p + off, p), - (p2 - 3, p2 - 2, p + off + 1, p + off), - (p2 - 2, p2 - 1, p + 1, p + off + 1), (p2 - 1, p2 - 4, p, p + 1)] - - # if slide add extra pane - sz, ph = I, h - if is_slider: - create_rectangle_pane(verts, faces, glass_indices, width, (h + I) / 2, 0.0, 0.0, I + (h + I) / 4, - [0.0, 0.0, 0.0, 0.0]) - sz = (h + I) / 2 - ph = h - (h - I) / 2 - y = 0.0 - - p = len(verts) - # border bottom - verts += [(-hw, y, sz), (hw, y, sz), (hw, y + t, sz), (-hw, y + t, sz), (-hw + I, y, sz + I), - (hw - I, y, sz + I), - (hw - I, y + t, sz + I), (-hw + I, y + t, sz + I), (-hw + I + bevelXZ, y + bevelY, sz + I + bevelXZ), - (hw - I - bevelXZ, y + bevelY, sz + I + bevelXZ), (hw - I, y + t - ei, sz + I), - (-hw + I, y + t - ei, sz + I)] - - # border middle - sz += ph - p3 = len(verts) - verts += [(-hw, y, sz), (hw, y, sz), (hw, y + t, sz), (-hw, y + t, sz), (-hw + I, y, sz), (hw - I, y, sz), - (hw - I, y + t, sz), (-hw + I, y + t, sz), (-hw + I + bevelXZ, y + bevelY, sz), - (hw - I - bevelXZ, y + bevelY, sz), (hw - I, y + t - ei, sz), (-hw + I, y + t - ei, sz)] - - # border arch - temp1 = [] - p2 = len(verts) - for i in arch_function(hw, arch_h, res): - temp1 += [(i[0], y, i[1] + h + I), (i[0], y + t, i[1] + h + I)] - temp2 = [] - for i in arch_function(hw - I, arch_h - I, res): - temp2 += [(i[0], y, i[1] + h + I), (i[0], y + t - ei, i[1] + h + I), (i[0], y + t, i[1] + h + I)] - temp3 = [] - for i in arch_function(hw - I - bevelXZ, arch_h - I - bevelXZ, res): - temp3 += [(i[0], y + bevelY, i[1] + h + I)] - - glass_indices += [len(faces) + 13, len(faces) + 14] - # bottom and side faces for border - faces += [(p, p + 1, p + 5, p + 4), (p + 4, p + 5, p + 9, p + 8), (p, p + 3, p + 2, p + 1), - (p + 2, p + 3, p + 7, p + 6), - (p + 6, p + 7, p + 11, p + 10), (p, p + 4, p + 16, p + 12), (p + 4, p + 8, p + 20, p + 16), - (p + 1, p + 13, p + 17, p + 5), - (p + 5, p + 17, p + 21, p + 9), (p + 2, p + 6, p + 18, p + 14), (p + 6, p + 10, p + 22, p + 18), - (p + 11, p + 7, p + 19, p + 23), - (p + 7, p + 3, p + 15, p + 19), (p + 8, p + 9, p + 21, p + 20), (p + 10, p + 11, p + 23, p + 22)] - - # combine vert lists with correct indexs for easier indexing - inner_face, outer_face = sort_window_verts(temp1, temp2, temp3, verts) - p4 = len(verts) - - # faces for arch - p = p2 - for i in range(res - 2): - faces += [(p, p + 2, p + 8, p + 6), (p + 2, p + 3, p + 9, p + 8), (p + 4, p + 5, p + 11, p + 10), - (p + 1, p + 7, p + 11, p + 5), - (p, p + 1, p + 7, p + 6)] - p += 6 - glass_indices += [len(faces), len(faces) + 1] - faces.append(inner_face) - faces.append(outer_face) - - glass_indices += [len(faces) + 10, len(faces) + 11] - # extra faces - faces += [(p3, p3 + 4, p3 + 14, p3 + 12), (p3 + 4, p3 + 8, p3 + 15, p3 + 14), - (p3 + 7, p3 + 3, p3 + 13, p3 + 17), - (p3 + 11, p3 + 7, p3 + 17, p3 + 16), (p3, p3 + 3, p3 + 13, p3 + 12), (p3 + 1, p4 - 6, p4 - 4, p3 + 5), - (p3 + 5, p4 - 4, p4 - 3, p3 + 9), - (p3 + 6, p3 + 10, p4 - 2, p4 - 1), (p3 + 2, p3 + 6, p4 - 1, p4 - 5), (p3 + 1, p4 - 6, p4 - 5, p3 + 2), - (p3 + 8, p3 + 9, p4 - 3, p3 + 15), - (p3 + 10, p3 + 11, p3 + 16, p4 - 2)] - - return verts, faces, glass_indices - - -# bay windows -def bay(width, h, is_bay, bay_angle, segments, split_center, db_hung, jamb_w, depth): - verts, faces, glass_indices = [], [], [] - segments = int(segments) - hw, jw, h2 = width / 2, jamb_w / 2, h - h -= 2 * I # adjust for height of jamb - - if is_bay: # bay - side_pw, th2 = (depth - jamb_w) / sin(bay_angle), radians(90) - bay_angle - x = hw - 0.5 * jamb_w * cos(th2) - side_pw * 0.5 * cos(bay_angle) - y = 0.5 * jamb_w * sin(th2) + side_pw * sin(bay_angle) * 0.5 - - for r in [bay_angle, -bay_angle]: - p = len(verts) - - matrix = Matrix.Translation((x, y, 0)) * Matrix.Rotation(radians(180) - r, 4, "Z") - create_rectangle_jamb(verts, faces, side_pw - 2 * I, h, jamb_w, True, 0) - - if db_hung: - create_rectangle_pane(verts, faces, glass_indices, side_pw, (h + I) / 2, 0, 0.0, I + (h + I) / 4, - [HI, 0.0, 0.0, 0.0]) - create_rectangle_pane(verts, faces, glass_indices, side_pw, (h + I) / 2, 0, (1.5 / METRIC_INCH), - I + (h + I) / 4 + (h - I) / 2, [0.0, 0.0, 0.0, 0.0]) - else: - create_rectangle_pane(verts, faces, glass_indices, side_pw, h, 0, 0.0, h / 2 + I, [HI, 0.0, 0.0, 0.0]) +# along with this program. If not, see . +from math import sqrt, radians, acos, asin, sin, cos, tan +from mathutils import Vector, Euler +from . jv_builder_base import JVBuilderBase +from . jv_utils import Units +from typing import Tuple, List - # rotation and translation - for v in range(p, len(verts), 1): - verts[v] = (matrix * Vector(verts[v])).to_tuple() - x *= -1 # flip for other side +class JVWindows(JVBuilderBase): + is_cutable = False + is_convertible = False - # center jambs and panes - hpw = hw - jamb_w * cos(th2) - side_pw * cos(bay_angle) - width_between_sides = 2 * hpw - 2 * I - pw = width_between_sides / 2 - I if split_center else width_between_sides - cx = -pw / 2 - I if split_center else 0 + @staticmethod + def draw(props, layout): + layout.prop(props, "window_pattern", icon="MOD_WIREFRAME") - for i in range(2 if split_center else 1): - p = len(verts) - create_rectangle_jamb(verts, faces, pw, h, jamb_w, True, 0) + if props.window_pattern in ("regular", "arch", "gothic"): + layout.separator() + row = layout.row() + row.prop(props, "window_width_medium") + row.prop(props, "window_height_medium") + elif props.window_pattern in ("polygon", "circular"): + layout.separator() + layout.prop(props, "window_radius") + elif props.window_pattern == "ellipse": + layout.separator() + row = layout.row() + row.prop(props, "window_width_wide") + row.prop(props, "window_height_short") + elif props.window_pattern in ("bow", "bay"): + layout.separator() + row = layout.row() + row.prop(props, "window_width_wide") + row.prop(props, "window_height_medium") - if db_hung: - create_rectangle_pane(verts, faces, glass_indices, pw, (h + I) / 2, 0, 0.0, I + (h + I) / 4, - [HI, 0.0, 0.0, 0.0]) - create_rectangle_pane(verts, faces, glass_indices, pw, (h + I) / 2, 0, (1.5 / METRIC_INCH), - I + (h + I) / 4 + (h - I) / 2, [0.0, 0.0, 0.0, 0.0]) - else: - create_rectangle_pane(verts, faces, glass_indices, pw, h, 0, 0.0, h / 2 + I, [HI, 0.0, 0.0, 0.0]) - - matrix = Matrix.Translation((cx, depth - jw, 0)) * Matrix.Rotation(radians(180), 4, "Z") - # rotation and translation - for v in range(p, len(verts), 1): - verts[v] = (matrix * Vector(verts[v])).to_tuple() - - cx += pw + 2 * I - - # filler strips - points = [((hw - jamb_w * cos(th2), 0), (hw, 0), (hw, jamb_w * sin(th2))), - ((-hw + jamb_w * cos(th2), 0), (-hw, jamb_w * sin(th2)), (-hw, 0)), - ((hpw, depth - jamb_w), (hpw + jamb_w * cos(th2), depth - jamb_w + jamb_w * sin(th2)), - (hpw, depth)), ((-hpw, depth - jamb_w), (-hpw, depth), - (-hpw - jamb_w * cos(th2), depth - jamb_w + jamb_w * sin(th2)),)] - - for pts in points: - p = len(verts) - for pt in pts: - verts += [(pt[0], pt[1], 0), (pt[0], pt[1], h2)] - faces += [(p, p + 2, p + 3, p + 1), (p + 2, p + 4, p + 5, p + 3), (p, p + 1, p + 5, p + 4), - (p + 1, p + 3, p + 5), (p, p + 4, p + 2)] - else: # bow - ang = radians(180) / segments - th2 = asin(jamb_w * sin(ang / 2) / hw) - pw = 2 * hw * sin(ang / 2 - th2) - 2 * I - r = hw * cos(ang / 2 - th2) - jamb_w / 2 - sx = jamb_w * cos(ang / 2) - - # first filler strip on far right - pt = point_rotation((hw, 0), (0, 0), th2) - adj_x = pt[0] - sx - verts += [(pt[0] - sx, 0, 0), (pt[0] - sx, 0, h2), (pt[0], 0, 0), (pt[0], 0, h2), (pt[0], pt[1], 0), - (pt[0], pt[1], h2)] - faces += [(0, 2, 3, 1), (2, 4, 5, 3), (0, 1, 5, 4), (1, 3, 5), (0, 4, 2)] - - for i in range(segments): - p = len(verts) - x, y = point_rotation((r, 0), (0, 0), ang * (i + 1) - ang / 2) - matrix = Matrix.Translation((x, y, 0)) * Matrix.Rotation(radians(90) + ang * i + ang / 2, 4, "Z") - create_rectangle_jamb(verts, faces, pw, h, jamb_w, True, 0) - - if db_hung: - create_rectangle_pane(verts, faces, glass_indices, pw, (h + I) / 2, 0, 0.0, I + (h + I) / 4, - [HI, 0.0, 0.0, 0.0]) - create_rectangle_pane(verts, faces, glass_indices, pw, (h + I) / 2, 0, (1.5 / METRIC_INCH), - I + (h + I) / 4 + (h - I) / 2, [0.0, 0.0, 0.0, 0.0]) - else: - create_rectangle_pane(verts, faces, glass_indices, pw, h, 0, 0.0, h / 2 + I, [HI, 0.0, 0.0, 0.0]) + layout.separator() + layout.prop(props, "jamb_width") + row = layout.row() + row.prop(props, "frame_width") + row.prop(props, "frame_thickness") + + if props.window_pattern in ("regular", "arch"): + layout.separator() + layout.prop(props, "slider", icon="DECORATE_OVERRIDE") - # rotation and translation - for v in range(p, len(verts), 1): - verts[v] = (matrix * Vector(verts[v])).to_tuple() + if props.slider and props.window_pattern == "regular": + layout.prop(props, "orientation", icon="ORIENTATION_VIEW") - # filler strips between panes/jambs on outside - p2 = len(verts) - if i == segments - 1: # last strip, adjust final point rotation - pts = point_rotation(((adj_x, 0), (hw, 0), (adj_x + sx, 0)), (0, 0), - (ang * (i + 1), ang * (i + 1) - th2, ang * (i + 1))) - else: - pts = point_rotation(((adj_x, 0), (hw, 0), (hw, 0)), (0, 0), - (ang * (i + 1), ang * (i + 1) - th2, ang * (i + 1) + th2)) - for pt in pts: - verts += [(pt[0], pt[1], 0), (pt[0], pt[1], h2)] - faces += [(p2, p2 + 2, p2 + 3, p2 + 1), (p2 + 2, p2 + 4, p2 + 5, p2 + 3), (p2, p2 + 1, p2 + 5, p2 + 4), - (p2 + 1, p2 + 3, p2 + 5), (p2, p2 + 4, p2 + 2)] - - return verts, faces, glass_indices - - -# circulars -def circular(radius, angle, res, jamb_w, full): - verts, faces, glass_indices = [], [], [] - - if full: - verts, faces, glass_indices = polygon(radius, res, jamb_w) - else: - w = jamb_w / 2 - edge_ang = radians(90) - acos(I / radius) - ang = (angle - 2*edge_ang) / res - - # jamb - verts += [(0, -w, 0), (0, w, 0)] - x, z = point_rotation((radius, 0), (0, 0), angle) - verts += [(x, -w, z), (x, w, z)] - - for i in range(res, -1, -1): - x, z = point_rotation((radius, 0), (0, 0), i*ang + edge_ang) - verts += [(x, -w, z), (x, w, z)] - - verts += [(radius, -w, 0), (radius, w, 0)] - off = len(verts) - edge_ang = radians(90) - acos(I / (radius - I)) # adjust angle for inch because of smaller radius - ang = (angle - 2*edge_ang) / res - - rot_x, rot_z = point_rotation((I / sin(angle / 2), 0), (0, 0), angle / 2) - verts += [(rot_x, -w, rot_z), (rot_x, w, rot_z)] - x, z = point_rotation((radius - I, 0), (0, 0), angle - edge_ang) - verts += [(x, -w, z), (x, w, z)] - - for i in range(res, -1, -1): - x, z = point_rotation((radius - I, 0), (0, 0), i * ang + edge_ang) - verts += [(x, -w, z), (x, w, z)] - verts += [(x, -w, z), (x, w, z)] - - p = 0 - for i in range(res + 3): - faces += [(p, p+off, p+off+2, p+2), (p, p+2, p+3, p+1), (p+off, p+off+1, p+off+3, p+off+2), - (p+1, p+3, p+off+3, p+off+1)] - p += 2 - - faces += [(p, p+off, off, 0), (p, p+1, 1, 0), (p+1, 1, off+1, p+off+1), (off, p+off, p+off+1, off+1)] - p = len(verts) - - # window - code is complicated mainly due to having to properly place corners to make sure no faces overlap - # that is why edge_ang is always being recalculated, to allow room for next tier of vertices - temp1 = [(rot_x, y, rot_z), (rot_x, y+t, rot_z)] # intital vertices at pivot - x, z = point_rotation((radius - I, 0), (0, 0), angle - edge_ang) - temp1 += [(x, y, z), (x, y+t, z)] # top corner - - edge_ang = radians(90) - acos(2 * I / (radius - 2 * I)) # adjust angle to make room for next tier of vertices - ang = (angle - 2*edge_ang) / res # get angle offset for new edge_ang - for i in range(res, -1, -1): - x, z = point_rotation((radius - I, 0), (0, 0), i * ang + edge_ang) - temp1 += [(x, y, z), (x, y+t, z)] - temp1 += [(radius - I, y, I), (radius - I, y + t, I)] - - rot_x, rot_z = point_rotation((2 * I / sin(angle / 2), 0), (0, 0), angle / 2) - temp2 = [(rot_x, y, rot_z), (rot_x, y+t-ei, rot_z), (rot_x, y+t, rot_z)] - x, z = point_rotation((radius - 2 * I, 0), (0, 0), angle - edge_ang) - temp2 += [(x, y, z), (x, y+t-ei, z), (x, y+t, z)] - - temp_ang = edge_ang # make copy to use later for placing last vertex - edge_ang = radians(90) - acos((2 * I + bevelXZ) / (radius - 2 * I - bevelXZ)) # adjust angle for smaller radius - ang = (angle - 2*edge_ang) / res - for i in range(res, -1, -1): - x, z = point_rotation((radius - 2 * I, 0), (0, 0), i * ang + edge_ang) - temp2 += [(x, y, z), (x, y+t-ei, z), (x, y+t, z)] - x, z = point_rotation((radius - 2 * I, 0), (0, 0), temp_ang) - temp2 += [(x, y, z), (x, y+t-ei, z), (x, y+t, z)] - - rot_x, rot_z = point_rotation(((2 * I + bevelXZ) / sin(angle / 2), 0), (0, 0), angle / 2) - temp3 = [(rot_x, y+bevelY, rot_z)] - x, z = point_rotation((radius - 2 * I - bevelXZ, 0), (0, 0), angle - edge_ang) - temp3 += [(x, y+bevelY, z)] - - x = 0.0 # retain x for use below - for i in range(res, -1, -1): - x, z = point_rotation((radius - 2 * I - bevelXZ, 0), (0, 0), i * ang + edge_ang) - temp3 += [(x, y+bevelY, z)] - temp3 += [(x, y + bevelY, 2 * I + bevelXZ)] - - inner_face, outer_face = sort_window_verts(temp1, temp2, temp3, verts) # sort vertices lists - glass_indices += [len(faces), len(faces) + 1] - faces.append(inner_face) - faces.append(outer_face) - - p2 = p - for i in range(res+3): - faces += [(p, p+2, p+8, p+6), (p+2, p+3, p+9, p+8), (p+4, p+5, p+11, p+10), (p+1, p+7, p+11, p+5), - (p, p+1, p+7, p+6)] - p += 6 - faces += [(p, p+2, p2+2, p2), (p+2, p+3, p2+3, p2+2), (p2+1, p2+5, p+5, p+1), (p2+5, p2+4, p+4, p+5)] - - return verts, faces, glass_indices - - -# creates jamb, startX define the bottom center of the window -def create_rectangle_jamb(verts, faces, width, height, jamb, full_jamb, start_x): - p = len(verts) - - # jambs - inch border around outside - cur_x = start_x - (width / 2) - I - cur_y = -(jamb / 2) - cur_z = 0.0 - - # counteract extra inch for jamb thickness on left side if not full_jamb - ex = I if not full_jamb else 0 - - verts += [(cur_x + ex, cur_y, cur_z), (cur_x + width + (2 * I), cur_y, cur_z), - (cur_x + width + (2 * I), cur_y + jamb, cur_z), (cur_x + ex, cur_y + jamb, cur_z), - (cur_x + I, cur_y, cur_z + I), (cur_x + width + I, cur_y, cur_z + I), - (cur_x + width + I, cur_y + jamb, cur_z + I), (cur_x + I, cur_y + jamb, cur_z + I)] - - cur_z += height + I - verts += [(cur_x + I, cur_y, cur_z), (cur_x + width + I, cur_y, cur_z), - (cur_x + width + I, cur_y + jamb, cur_z), (cur_x + I, cur_y + jamb, cur_z), - (cur_x + ex, cur_y, cur_z + I), (cur_x + width + (2 * I), cur_y, cur_z + I), - (cur_x + width + (2 * I), cur_y + jamb, cur_z + I), (cur_x + ex, cur_y + jamb, cur_z + I)] - - # top and bottom - for i in range(2): - faces += [(p + 0, p + 1, p + 5, p + 4), (p + 4, p + 5, p + 6, p + 7), (p + 0, p + 3, p + 2, p + 1), - (p + 2, p + 3, p + 7, p + 6)] - p += 8 - - p -= 16 - if full_jamb: # don't do left side - faces += [(p + 0, p + 4, p + 8, p + 12), (p + 0, p + 12, p + 15, p + 3), (p + 3, p + 15, p + 11, p + 7), - (p + 4, p + 7, p + 11, p + 8)] - - faces += [(p + 1, p + 2, p + 14, p + 13), (p + 1, p + 13, p + 9, p + 5), (p + 5, p + 9, p + 10, p + 6), - (p + 2, p + 6, p + 10, p + 14)] - - -# add faces and verts to (verts and faces) -# start_y and start_z need to be center of window pane at inside on y -# wide_frame is extra [bottom, right, top, left] -def create_rectangle_pane(verts, faces, glass_indices, width, height, sx, sy, sz, wide_frame): - p, f = len(verts), len(faces) - w, h = width / 2, height / 2 - - # loops allow just one corner to be done, and then all the rest is done by just inverting values - for i2 in range(1, -2, -2): # invert for top and bottom - ex = wide_frame[0] if i2 == 1 else -wide_frame[2] - - for i in range(-1, 2, 2): # invert for sides - ex2 = wide_frame[3] if i == -1 else -wide_frame[1] - - verts += [(sx + i * w, sy - t, sz - i2 * h), (sx + i * (w - I) + ex2, sy - t, sz - i2 * (h - I) + ex), - (sx + i * (w - I - bevelXZ) + ex2, sy - t + bevelY, sz - i2 * (h - I - bevelXZ) + ex), - (sx + i * (w - I) + ex2, sy - t + bevelY, sz - i2 * (h - I) + ex), - (sx + i * (w - I) + ex2, sy - ei, sz - i2 * (h - I) + ex), - (sx + i * (w - I) + ex2, sy, sz - i2 * (h - I) + ex), (sx + i * w, sy, sz - i2 * h)] - - # faces - faces += [(p+0, p+7, p+8, p+1), (p+1, p+8, p+9, p+2), (p+6, p+5, p+12, p+13)] - p += 14 - faces += [(p+0, p+1, p+8, p+7), (p+1, p+2, p+9, p+8), (p+6, p+13, p+12, p+5)] - p -= 14 - faces += [(p, p+1, p+15, p+14), (p+1, p+2, p+16, p+15), (p, p+14, p+20, p+6), - (p+6, p+20, p+19, p+5), (p+4, p+5, p+19, p+18)] - p += 7 - faces += [(p, p+14, p+15, p+1), (p+1, p+15, p+16, p+2), (p, p+6, p+20, p+14), - (p+6, p+5, p+19, p+20), (p+4, p+18, p+19, p+5)] - p -= 7 - - faces += [(p+2, p+9, p+23, p+16), (p+4, p+18, p+25, p+11), (p, p+6, p+13, p+7), (p+14, p+21, p+27, p+20), - (p+2, p+3, p+17, p+16), (p+3, p+4, p+17, p+18), (p+11, p+10, p+24, p+25), (p+10, p+9, p+23, p+24), - (p+4, p+5, p+12, p+11), (p+18, p+19, p+26, p+25)] - - glass_indices += [f + 16, f + 17] - - -def double_hung(width, height, jamb, num): - verts, faces, glass_indices = [], [], [] - sx = -(num - 1) / 2 * width - (num - 1) * HI - full_jamb = True - - for i in range(num): - create_rectangle_jamb(verts, faces, width, height, jamb, full_jamb, sx) - - # panes - create_rectangle_pane(verts, faces, glass_indices, width, (height + I) / 2, sx, 0.0, I + (height + I) / 4, - [HI, 0.0, 0.0, 0.0]) - create_rectangle_pane(verts, faces, glass_indices, width, (height + I) / 2, sx, (1.5 / METRIC_INCH), - I + (height + I) / 4 + (height - I) / 2, [0.0, 0.0, 0.0, 0.0]) - - sx += width + I - full_jamb = False - - return verts, faces, glass_indices - - -def gliding(width, height, slide_right, jamb_w): - verts, faces, glass_indices = [], [], [] - - create_rectangle_jamb(verts, faces, width, height, jamb_w, True, 0.0) - - if slide_right: - create_rectangle_pane(verts, faces, glass_indices, (width + I) / 2, height, -(width - I) / 4, 0.0, - (height + 2 * I) / 2, [0.0, HI, 0.0, I]) - create_rectangle_pane(verts, faces, glass_indices, (width + 2 * I) / 2, height, (width - 2 * I) / 4, I + HI, - (height + 2 * I) / 2, [0.0, 0.0, 0.0, HI]) - else: - create_rectangle_pane(verts, faces, glass_indices, (width + I) / 2, height, -(width - I) / 4, I + HI, - (height + 2 * I) / 2, [0.0, 0.0, 0.0, HI]) - create_rectangle_pane(verts, faces, glass_indices, (width + I) / 2, height, (width - I) / 4, 0.0, - (height + 2 * I) / 2, [0.0, HI, 0.0, I]) - - return verts, faces, glass_indices - - -def gothic(width, height, res, is_slider, jamb_w): - # make res even because IntProperty step isn't not currently supported - if res % 2 != 0: - res += 1 - - verts, faces, glass_indices = [], [], [] - hw, w = width / 2, jamb_w / 2 - hres = int(res / 2) - - edge_height = height - sqrt(3)*hw - end_angles = [acos((hw + I) / (width + 2 * I)), acos((hw + I) / (width + I)), acos((hw + I) / width), - acos((hw + I) / (width - bevelXZ))] - - y = 0 if is_slider else -t / 2 - h_off = edge_height / 2 if is_slider else 0 - - # lists keep from having multiple sections of codes with only slight differences - outer_inner = [[I, end_angles[0], 0.0], [0.0, end_angles[1], I]] # width offset, end_angle, leg height offset - offsets = [[0, int(res/2) + 1, -1, 1], [int(res/2) - 1, -1, 1, -1]] # start, stop, mod1/angle side, mod2/step - - for oi in outer_inner: - verts += [(-hw-oi[0], -w, oi[2]), (-hw-oi[0], w, oi[2])] - ang_off = oi[1] / hres - - for g in offsets: - for i in range(g[0], g[1], g[3]): - x, z = point_rotation((g[2] * (hw+oi[0]), edge_height), - (g[3] * (hw + I), edge_height), g[2] * ang_off * i) - verts += [(x, -w, z), (x, w, z)] - - verts += [(hw+oi[0], -w, oi[2]), (hw+oi[0], w, oi[2])] - off = int(len(verts)/2) - p = 0 - - for i in range(res+2): - faces += [(p, p+off, p+off+2, p+2), (p+off, p+off+1, p+off+3, p+off+2), (p, p+2, p+3, p+1), - (p+1, p+3, p+off+3, p+off+1)] - p += 2 - faces += [(p, p+off, off, 0), (p, p+1, 1, 0), (1, off+1, p+off+1, p+1), (off, p+off, p+off+1, off+1)] - - # window - temp1 = [(-hw, y, I + h_off), (-hw, y + t, I + h_off)] - ang_off = end_angles[1] / hres - for g in offsets: - for i in range(g[0], g[1], g[3]): - x, z = point_rotation((g[2] * hw, edge_height), (g[3] * (hw + I), edge_height), g[2] * ang_off * i) - temp1 += [(x, y, z), (x, y+t, z)] - temp1 += [(hw, y, I + h_off), (hw, y + t, I + h_off)] - - temp2 = [(-hw + I, y, 2 * I + h_off), (-hw + I, y + t - ei, 2 * I + h_off), (-hw + I, y + t, 2 * I + h_off)] - ang_off = end_angles[2] / hres - for g in offsets: - for i in range(g[0], g[1], g[3]): - x, z = point_rotation((g[2] * (hw - I), edge_height), (g[3] * (hw + I), edge_height), g[2] * ang_off * i) - temp2 += [(x, y, z), (x, y+t-ei, z), (x, y+t, z)] - temp2 += [(hw - I, y, 2 * I + h_off), (hw - I, y + t - ei, 2 * I + h_off), (hw - I, y + t, 2 * I + h_off)] - - temp3 = [(-hw + I + bevelXZ, y + bevelY, 2 * I + bevelXZ + h_off)] - ang_off = end_angles[3] / hres - for g in offsets: - for i in range(g[0], g[1], g[3]): - x, z = point_rotation((g[2] * (hw - I - bevelXZ), edge_height), - (g[3] * (hw + I), edge_height), g[2] * ang_off * i) - temp3 += [(x, y+bevelY, z)] - temp3 += [(hw - I - bevelXZ, y + bevelY, 2 * I + bevelXZ + h_off)] - - inner_face, outer_face = sort_window_verts(temp1, temp2, temp3, verts) - p2 = p+off+2 - glass_indices += [len(faces), len(faces) + 1] - faces.append(inner_face) - faces.append(outer_face) - - p += off+2 - for i in range(res+2): - faces += [(p, p+2, p+8, p+6), (p+2, p+3, p+9, p+8), (p+4, p+5, p+11, p+10), (p+1, p+7, p+11, p+5), - (p, p+1, p+7, p+6)] - p += 6 - faces += [(p2, p, p+2, p2+2), (p2, p2+1, p+1, p), (p+1, p2+1, p2+5, p+5), (p2+5, p2+4, p+4, p+5), - (p2+2, p+2, p+3, p2+3)] - - if is_slider: - create_rectangle_pane(verts, faces, glass_indices, width, h_off + t, 0.0, y, h_off / 2 + I + t / 2, - [t - I for i in range(4)]) - - return verts, faces, glass_indices - - -def oval(width, height, res, jamb_w): - # make res even because IntProperty step isn't not currently supported - if res % 2 != 0: - res += 1 - - verts, faces, glass_indices = [], [], [] - - hw = width / 2 - hh = height / 2 - w = jamb_w / 2 - hres = int(res / 2) - - # jamb - for offset in [I, 0.0]: # top - verts += [(-hw - offset, -w, 0.0), (-hw - offset, w, 0.0)] - for i in arch_function(hw + offset, hh + offset, hres): - verts += [(i[0], -w, i[1]), (i[0], w, i[1])] - verts += [(hw + offset, -w, 0.0), (hw + offset, w, 0.0)] - - # insert vertices at key positions to make the inner and outer loop vertices consecutively numbered - p = int(len(verts) / 2) - for i in arch_function(hw + I, hh + I, hres): - verts.insert(p, (i[0], w, -i[1])) - verts.insert(p, (i[0], -w, -i[1])) - - e = len(verts) - for i in arch_function(hw, hh, hres): - verts.insert(e, (i[0], w, -i[1])) - verts.insert(e, (i[0], -w, -i[1])) - - p = int(len(verts) / 2) - # top faces - for i in range(0, 2*res - 2, 2): - faces += [(i, i+2, i+3, i+1), (i, i+p, i+p+2, i+2), (i+p, i+p+1, i+p+3, i+p+2), (i+p+1, i+1, i+3, i+p+3)] - - # left side connecting faces - e = len(verts) - 1 - faces += [(p-2, e-1, p, 0), (p-2, 0, 1, p-1), (e, p-1, 1, p+1), (e-1, e, p+1, p)] - - # window - TOP - temp1 = [(-hw, y, 0.0), (-hw, y+t, 0.0)] - for i in arch_function(hw, hh, hres): # outer two vertices - temp1 += [(i[0], y, i[1]), (i[0], y+t, i[1])] - temp1 += [(hw, y, 0.0), (hw, y+t, 0.0)] - insert1 = len(temp1) - - temp2 = [(-hw + I, y, 0.0), (-hw + I, y + t - ei, 0.0), (-hw + I, y + t, 0.0)] - for i in arch_function(hw - I, hh - I, hres): # center three vertices - temp2 += [(i[0], y, i[1]), (i[0], y + t - ei, i[1]), (i[0], y + t, i[1])] - temp2 += [(hw - I, y, 0.0), (hw - I, y + t - ei, 0.0), (hw - I, y + t, 0.0)] - insert2 = len(temp2) - - temp3 = [(-hw + I + bevelXZ, y + bevelY, 0.0)] - for i in arch_function(hw - I - bevelXZ, hh - I - bevelXZ, hres): # inner vertex - temp3 += [(i[0], y + bevelY, i[1])] - temp3 += [(hw - I - bevelXZ, y + bevelY, 0.0)] - insert3 = len(temp3) - - # window - BOTTOM - for i in arch_function(hw, hh, hres): # outer two vertices - temp1.insert(insert1, (i[0], y+t, -i[1])) - temp1.insert(insert1, (i[0], y, -i[1])) - - for i in arch_function(hw - I, hh - I, hres): # center three vertices - temp2.insert(insert2, (i[0], y + t, -i[1])) - temp2.insert(insert2, (i[0], y + t - ei, -i[1])) - temp2.insert(insert2, (i[0], y, -i[1])) - - for i in arch_function(hw - I - bevelXZ, hh - I - bevelXZ, hres): # inner vertex - temp3.insert(insert3, (i[0], y + bevelY, -i[1])) - - # put all vertices in correct order into main list - inner_face, outer_face = sort_window_verts(temp1, temp2, temp3, verts) - p = e + 1 - glass_indices += [len(faces), len(faces) + 1] - faces += [inner_face, outer_face] - for i in range(res - 1): - faces += [(p, p+2, p+8, p+6), (p+2, p+3, p+9, p+8), (p+4, p+5, p+11, p+10), (p+1, p+7, p+11, p+5), - (p, p+1, p+7, p+6)] - p += 6 - faces += [(e+1, p, p+2, e+3), (p+2, p+3, e+4, e+3), (p+4, p+5, e+5, e+6), (p+5, p+1, e+2, e+6), (e+1, p, p+1, e+2)] - - return verts, faces, glass_indices - - -def polygon(radius, sides, jamb_w): - verts, faces, glass_indices = [], [], [] - - ang = 360 / sides - w = jamb_w / 2 - th = I / cos(radians(ang) / 2) # adjust inch because of angle - - # jamb verts - for i in range(sides): - x, z = point_rotation((radius, 0.0), (0.0, 0.0), radians(i*ang + 90)) - verts += [(x, -w, z), (x, w, z)] - off = len(verts) - - for i in range(sides): - x, z = point_rotation((radius-th, 0.0), (0.0, 0.0), radians(i*ang + 90)) - verts += [(x, -w, z), (x, w, z)] - - # jamb faces - p = 0 - for i in range(sides-1): - faces += [(p, p+1, p+3, p+2), (p, p+2, p+2+off, p+off), (p+1, p+1+off, p+3+off, p+3), - (p+off, p+2+off, p+3+off, p+1+off)] - p += 2 - faces += [(p, p+1, 1, 0), (p, 0, off, p+off), (p+1, p+1+off, 1+off, 1), (p+off, off, 1+off, p+1+off)] - - temp1 = [] - for i in range(sides): - x, z = point_rotation((radius - th, 0.0), (0.0, 0.0), radians(i*ang + 90)) - temp1 += [(x, y, z), (x, y+t, z)] - - temp2 = [] - for i in range(sides): - x, z = point_rotation((radius - 2*th, 0.0), (0.0, 0.0), radians(i*ang + 90)) - temp2 += [(x, y, z), (x, y+t-ei, z), (x, y+t, z)] - - temp3 = [] - for i in range(sides): - x, z = point_rotation((radius - 2*th - bevelXZ, 0.0), (0.0, 0.0), radians(i*ang + 90)) - temp3 += [(x, y+bevelY, z)] - - inner_face, outer_face = sort_window_verts(temp1, temp2, temp3, verts) - inner_face.reverse() # reverse faces because verts were done counterclockwise, not clockwise - outer_face.reverse() - - # pane faces - glass_indices += [len(faces), len(faces) + 1] - faces.append(inner_face) - faces.append(outer_face) - p += off+2 - p2 = p - for i in range(sides - 1): - faces += [(p, p+6, p+8, p+2), (p+2, p+8, p+9, p+3), (p+4, p+10, p+11, p+5), (p+1, p+5, p+11, p+7), - (p, p+6, p+7, p+1)] - p += 6 - faces += [(p2, p2+2, p+2, p), (p2, p, p+1, p2+1), (p+1, p+5, p2+5, p2+1), (p2+5, p+5, p+4, p2+4), - (p2+2, p2+3, p+3, p+2)] - - return verts, faces, glass_indices - - -# takes separate groups of vertices that compose the window pane and adds them into verts in a manner that makes it easy -# to create faces -def sort_window_verts(temp1, temp2, temp3, verts): - inner_face = [] - outer_face = [] - p_final = len(verts) - for i in range(int(len(temp1) / 2)): - verts += temp1[0:2] - del temp1[0:2] - verts += [temp2[0]] - verts += [temp3[0]] - verts += temp2[1:3] - del temp2[0:3] - del temp3[0] - inner_face.insert(0, p_final+3) - outer_face.append(p_final+4) - p_final += 6 - - return inner_face, outer_face - - -def stationary(width, height, jamb_w, num): - verts, faces, glass_indices = [], [], [] - sx = -(num - 1) / 2 * width - (num - 1) * HI - full_jamb = True - - for i in range(num): - create_rectangle_jamb(verts, faces, width, height, jamb_w, full_jamb, sx) - create_rectangle_pane(verts, faces, glass_indices, width, height, sx, 0.75 / METRIC_INCH, (height + 2 * I) / 2, - [0.0, 0.0, 0.0, 0.0]) - - sx += width + I - full_jamb = False - - return verts, faces, glass_indices - - -# create window based off of parameters -def create_window(context, types, sub_type, jamb_w, dh_width, dh_height, gang_num, gl_width, gl_height, gl_slide_right, - so_width, so_height, so_height_tall, sides, radius, is_full_circle, angle, roundness, res, is_slider, - ba_width, ba_height, is_bay, bay_angle, segments, is_split_center, is_double_hung, depth): - - verts, faces, ob_to_join, glass_indices = [], [], [], [] - - if types == "1": - verts, faces, glass_indices = double_hung(dh_width, dh_height, jamb_w, gang_num) - elif types == "2": - verts, faces, glass_indices = gliding(gl_width, gl_height, gl_slide_right, jamb_w) - elif types == "3": - verts, faces, glass_indices = stationary(so_width, so_height, jamb_w, gang_num) - # odd-shaped - elif types == "4": - if sub_type == "1": - verts, faces, glass_indices = polygon(radius, sides, jamb_w) - elif sub_type == "2": - verts, faces, glass_indices = circular(radius, angle, res, jamb_w, is_full_circle) - elif sub_type == "3": - verts, faces, glass_indices = arch(so_width, so_height, roundness, res, is_slider, jamb_w) - elif sub_type == "4": - verts, faces, glass_indices = gothic(so_width, so_height_tall, res, is_slider, jamb_w) - elif sub_type == "5": - verts, faces, glass_indices = oval(so_width, so_height, res, jamb_w) - elif types == "5": - verts, faces, glass_indices = bay(ba_width, ba_height, is_bay, bay_angle, segments, is_split_center, - is_double_hung, jamb_w, depth) - - return verts, faces, ob_to_join, glass_indices - - -# update window -def update_window(self, context): - ob = context.object - - mats = [] - for mat in ob.data.materials: - mats.append(mat.name) - - verts, faces, ob_to_join, gl_is = create_window(context, ob.jv_w_types, ob.jv_odd_types, ob.jv_jamb_width, - ob.jv_dh_width, ob.jv_dh_height, ob.jv_gang_num, ob.jv_gl_width, - ob.jv_gl_height, ob.jv_gl_slide_right, ob.jv_so_width, - ob.jv_so_height, ob.jv_so_height_tall, ob.jv_sides, ob.jv_o_radius, - ob.jv_full_circle, ob.jv_w_angle, ob.jv_roundness, ob.jv_resolution, - ob.jv_is_slider, ob.jv_ba_width, ob.jv_ba_height, ob.jv_is_bay, - ob.jv_bay_angle, ob.jv_bow_segments, ob.jv_is_split_center, - ob.jv_is_double_hung, ob.jv_depth) - - old_mesh = ob.data - mesh = bpy.data.meshes.new(name="window") - mesh.from_pydata(verts, [], faces) - mesh.update(calc_edges=True) - ob.data = mesh - - # remove old mesh - for i in bpy.data.objects: - if i.data == old_mesh: - i.data = mesh - - old_mesh.user_clear() - bpy.data.meshes.remove(old_mesh) - - # assign materials to correct faces, first material is frame, second is glass - if len(mats) >= 1: - ob.data.materials.append(bpy.data.materials[mats[0]]) - else: - mat = bpy.data.materials.new(ob.name + "_frame") - mat.use_nodes = True - ob.data.materials.append(mat) - - if len(mats) >= 2: - ob.data.materials.append(bpy.data.materials[mats[1]]) - else: - mat = bpy.data.materials.new(ob.name + "_glass") - mat.use_nodes = True - ob.data.materials.append(mat) - - assign_glass_faces(context, gl_is) - - if ob.jv_is_unwrap: - unwrap_object(self, context) - if ob.jv_is_random_uv: - random_uvs(self, context) - - -def assign_glass_faces(context, indices): - o = context.object - - for i in indices: - o.data.polygons[i].material_index = 1 - - -def window_materials(self, context): - o, mat = context.object, None - - if o.jv_color_image == "rgba": - mat = glossy_diffuse_material(bpy, o.jv_rgba_color, (1, 1, 1), 0.05, 0.1, o.name + "_frame") - else: - if o.jv_col_image == "": - self.report({"ERROR"}, "JARCH Vis: No Color Image Filepath") - return - if o.jv_is_bump and o.jv_norm_image == "": - self.report({"ERROR"}, "JARCH VIs: No Normal Image Filepath") - return - - mat = image_material(bpy, o.jv_im_scale, o.jv_col_image, o.jv_norm_image, o.jv_bump_amo, o.jv_is_bump, - o.name + "_frame", True, 0.1, 0.05, o.jv_is_rotate, None) - - if mat is None: - self.report({"ERROR"}, "JARCH Vis: Invalid Filepath Found When Creating Material") - return - elif len(o.data.materials) >= 1: - o.data.materials[0] = mat - elif len(o.data.materials) == 0: - o.data.materials.append(mat) - - mat2 = architectural_glass_material(bpy, (1, 1, 1, 1), o.name + "_glass") - if len(o.data.materials) >= 2: - o.data.materials[1] = mat2 - else: - o.data.materials.append(mat2) - - for i in bpy.data.materials: - if i.users == 0: - bpy.data.materials.remove(i) - - -class WindowMaterials(bpy.types.Operator): - bl_idname = "mesh.jv_window_materials" - bl_label = "Generate\\Update Materials" - bl_options = {"UNDO", "INTERNAL"} - - def execute(self, context): - window_materials(self, context) - return {"FINISHED"} - - -class WindowPanel(bpy.types.Panel): - bl_idname = "OBJECT_PT_jv_window" - bl_label = "JARCH Vis: Window" - bl_space_type = "VIEW_3D" - bl_region_type = "TOOLS" - bl_category = "JARCH Vis" - - def draw(self, context): - layout = self.layout - if bpy.context.mode == "EDIT_MESH": - layout.label("JARCH Vis: Windows Doesn't Work In Edit Mode", icon="ERROR") - elif context.object is None: - layout.operator("mesh.jv_window_add", icon="OUTLINER_OB_LATTICE") - elif context.object.jv_internal_type == "window": - o = context.object - layout.label("Window Type:") - layout.prop(o, "jv_w_types", icon="OBJECT_DATAMODE") - - if o.jv_w_types == "4": - layout.label("Shape:") - layout.prop(o, "jv_odd_types", icon="SPACE2") - - # parameters + if props.window_pattern == "polygon": layout.separator() - layout.prop(o, "jv_jamb_width") + layout.prop(props, "window_side_count") - # double hung + if props.window_pattern == "circular": layout.separator() - if o.jv_w_types == "1": - layout.prop(o, "jv_dh_width") - layout.prop(o, "jv_dh_height") - layout.prop(o, "jv_gang_num") - # gliding - elif o.jv_w_types == "2": - layout.prop(o, "jv_gl_width") - layout.prop(o, "jv_gl_height") - layout.prop(o, "jv_gl_slide_right", icon="FORWARD") - # stationary - elif o.jv_w_types == "3": - layout.prop(o, "jv_so_width") - layout.prop(o, "jv_so_height") - layout.prop(o, "jv_gang_num") - # odd-shaped - elif o.jv_w_types == "4": - # polygon - if o.jv_odd_types == "1": - layout.prop(o, "jv_o_radius") - layout.prop(o, "jv_sides") - # circular - elif o.jv_odd_types == "2": - layout.prop(o, "jv_o_radius") - layout.prop(o, "jv_resolution") - layout.prop(o, "jv_full_circle", icon="MESH_CIRCLE") - if not o.jv_full_circle: - layout.prop(o, "jv_w_angle") + row = layout.row() + row.prop(props, "full_circle", icon="MESH_CIRCLE") - else: - layout.prop(o, "jv_so_width") - - if o.jv_odd_types == "4": # gothic - layout.prop(o, "jv_so_height_tall") - layout.prop(o, "jv_is_slider", icon="SETTINGS") - else: - layout.prop(o, "jv_so_height") - - layout.separator() - layout.prop(o, "jv_resolution") - - # arch - if o.jv_odd_types == "3": - layout.prop(o, "jv_roundness") - layout.prop(o, "jv_is_slider", icon="SETTINGS") - - # bay - elif o.jv_w_types == "5": - layout.prop(o, "jv_ba_width") - layout.prop(o, "jv_ba_height") - layout.separator() - - layout.prop(o, "jv_is_bay", icon="NOCURVE") - if o.jv_is_bay: - layout.prop(o, "jv_bay_angle", icon="MAN_ROT") - layout.prop(o, "jv_depth") - layout.separator() - layout.prop(o, "jv_is_split_center", icon="PAUSE") - else: - layout.prop(o, "jv_bow_segments") - layout.prop(o, "jv_is_double_hung", icon="SPLITSCREEN") + if not props.full_circle: + row.prop(props, "window_angle") + if props.window_pattern == "bay": layout.separator() - layout.prop(o, "jv_is_unwrap", icon="GROUP_UVS") - if o.jv_is_unwrap: - layout.prop(o, "jv_is_random_uv", icon="RNDCURVE") + row = layout.row() + row.prop(props, "bay_angle") + row.prop(props, "window_depth") - # materials + if props.window_pattern == "bow": layout.separator() - if context.scene.render.engine == "CYCLES": - layout.prop(o, "jv_is_material", icon="MATERIAL") - else: - layout.label("Materials Only Supported With Cycles", icon="POTATO") + layout.prop(props, "bow_segments") - if o.jv_is_material and context.scene.render.engine == "CYCLES": - layout.separator() - layout.label("Frame Material:") - layout.prop(o, "jv_color_image") + if props.window_pattern == "arch": + layout.separator() + layout.prop(props, "window_roundness") - if o.jv_color_image == "rgba": - layout.prop(o, "jv_rgba_color") + if props.window_pattern in ("arch", "gothic", "ellipse", "circular"): + layout.separator() + layout.prop(props, "window_resolution") + + if props.window_pattern == "regular": + layout.separator() + layout.prop(props, "num_joined_windows") + + @staticmethod + def update(props, context): + mesh = JVWindows._start(context) + + getattr(JVWindows, "_{}".format(props.window_pattern))(props, mesh) + + JVWindows._finish(context, mesh) + JVWindows._uv_unwrap(by_seams=False) + + @staticmethod + def _update_mesh_from_geometry_lists(mesh, geometry_lists: List[Tuple[list, list, list]]): + """ + Take all the generated geometry data and combine it into one mesh, offset face vertex index's where needed + :param mesh: the bmesh mesh + :param geometry_lists: a list of tuples of verts, faces, glass_ids + :return: + """ + vert_offset = 0 + face_offset = 0 + for verts, faces, glass_ids in geometry_lists: + for v in verts: + mesh.verts.new(v) + mesh.verts.ensure_lookup_table() + + for face in faces: + mesh.faces.new([mesh.verts[i+vert_offset] for i in face]) + mesh.faces.ensure_lookup_table() + + for fid in glass_ids: + mesh.faces[fid+face_offset].material_index = 1 + + vert_offset = len(mesh.verts) + face_offset = len(mesh.faces) + + @staticmethod + def _regular(props, mesh): + width, height, jamb_w = props.window_width_medium, props.window_height_medium, props.jamb_width + frame_width, pane_th, jamb_th = props.frame_width, props.frame_thickness, Units.INCH + + if props.orientation == "vertical": + pane_width = width + if props.slider: + pane_height = (height + frame_width) / 2 + else: + pane_height = height + else: + pane_height = height + if props.slider: + pane_width = (width + frame_width) / 2 + else: + pane_width = width + + geometry_data = [] + x = 0 + for _ in range(props.num_joined_windows): + tx = x + # create jamb - keep left if first jamb + geometry_data.append(JVWindows._rectangular_jamb_geometry(width, height, (x, 0, 0), jamb_w, + keep_left=x == 0, th=jamb_th)) + + # create first pane + y, z, = 0, jamb_th + if props.slider: # first pane is offset on y if there is going to be a second pane + y = -pane_th / 2 + + geometry_data.append(JVWindows._rectangular_pane_geometry(pane_width, pane_height, (x+jamb_th, y, z), + frame_th=pane_th, fw=frame_width)) + + # # create second pane if needed + if props.slider: + y += pane_th + if props.orientation == "vertical": + z += pane_height - frame_width else: - layout.prop(o, "jv_col_image", icon="COLOR") - layout.prop(o, "jv_is_bump", icon="SMOOTHCURVE") + x += pane_width - frame_width + + geometry_data.append(JVWindows._rectangular_pane_geometry(pane_width, pane_height, (x+jamb_th, y, z), + frame_th=pane_th, fw=frame_width)) + + x = tx + width + jamb_th + + JVWindows._update_mesh_from_geometry_lists(mesh, geometry_data) + + @staticmethod + def _arch(props, mesh): + geometry_data = [] + verts, faces, glass_ids = [], [], [] + + # main values + width, height, jamb_w = props.window_width_medium, props.window_height_medium, props.jamb_width + res, roundness = props.window_resolution, props.window_roundness / 100 + a, b, = width / 2, (width * roundness) / 2 # a, b for main window, need modified for jamb, inset frame, etc. + frame_width, jamb_th, frame_th, inset = props.frame_width, Units.INCH, props.frame_thickness, Units.Q_INCH + + # useful to save calculations later + hjamb_w, hframe_width, hframe_th = jamb_w / 2, frame_width / 2, frame_th / 2 + center_x, center_z = jamb_th + (width / 2), jamb_th + height - b + vpl = res + 4 # vertices per loop + + # JAMB ---------------------------------------------------------------------- + steps = [ # start x, end x, y, z, amount to add to a and b + (jamb_th, jamb_th+width, -hjamb_w, jamb_th, 0), # inner on -Y + (0, (2*jamb_th) + width, -hjamb_w, 0, jamb_th), # outer on -Y + (0, (2 * jamb_th) + width, hjamb_w, 0, jamb_th), # outer on Y + (jamb_th, jamb_th + width, hjamb_w, jamb_th, 0) # inner on Y + ] + + for sx, ex, y, sz, dab in steps: + verts += [(sx, y, sz), (ex, y, sz)] + for x, z in JVWindows._ellipse_iterator(a+dab, b+dab, res): + verts.append((center_x+x, y, center_z+z)) + + # jamb faces + JVWindows._loop_face_builder(len(steps), vpl, faces) + geometry_data.append((verts, faces, glass_ids)) + + # PANE(S) ---------------------------------------------------------------------------------- + verts, faces, glass_ids = [], [], [] # clear for next round + sy, sz = 0, jamb_th # start y and z + if props.slider: + pane_height = center_z / 2 + frame_width + geometry_data.append(JVWindows._rectangular_pane_geometry(width, pane_height, + (jamb_th, -hframe_th, jamb_th), + fw=frame_width, inset=inset, + frame_th=frame_th)) + + sy, sz = frame_th / 2, jamb_th + pane_height - frame_width + + # main pane + steps = [ # start x, end x, y, z, amount to add to a and b + (jamb_th+frame_width, jamb_th+width-frame_width, sy-hframe_th+inset, sz+frame_width, -frame_width), + (jamb_th+frame_width, jamb_th+width-frame_width, sy-hframe_th, sz+frame_width, -frame_width), + (jamb_th, jamb_th+width, sy-hframe_th, sz, 0), + + (jamb_th, jamb_th+width, sy+hframe_th, sz, 0), + (jamb_th+frame_width, jamb_th+width-frame_width, sy+hframe_th, sz+frame_width, -frame_width), + (jamb_th+frame_width, jamb_th+width-frame_width, sy+hframe_th-inset, sz+frame_width, -frame_width), + ] + + # vertices + for sx, ex, y, sz, dab in steps: + verts += [(sx, y, sz), (ex, y, sz)] + for x, z in JVWindows._ellipse_iterator(a+dab, b+dab, res): + verts.append((center_x+x, y, center_z+z)) + + # faces + JVWindows._loop_face_builder(len(steps), vpl, faces) + JVWindows._close_glass_faces_in_loops(len(verts), vpl, faces, glass_ids) + geometry_data.append((verts, faces, glass_ids)) + + JVWindows._update_mesh_from_geometry_lists(mesh, geometry_data) + + @staticmethod + def _polygon(props, mesh): + geometry_data = [] + + radius, jamb_w, jamb_th, sides = props.window_radius, props.jamb_width, Units.INCH, props.window_side_count + frame_width, frame_th, inset = props.frame_width, props.frame_thickness, Units.Q_INCH # pane variables + interior_angle = ((sides - 2) * radians(180)) / sides + + # adjust so these are the widths/thicknesses perpendicular to the sides + jamb_th /= sin(interior_angle / 2) + frame_width /= sin(interior_angle / 2) + + ang_step = Euler((0, radians(360) / sides, 0)) + hjamb_w, hframe_th, = jamb_w / 2, frame_th / 2 + + # jamb and pane + steps = [ # one set of steps for the jamb, one set of steps for the pane + ( + (radius, -hjamb_w), (radius+jamb_th, -hjamb_w), (radius+jamb_th, hjamb_w), (radius, hjamb_w) + ), + ( + (radius-frame_width, -hframe_th + inset), (radius-frame_width, -hframe_th), + (radius, -hframe_th), (radius, hframe_th), + (radius-frame_width, hframe_th), (radius-frame_width, hframe_th - inset) + ) + ] + + for sub_steps in steps: + verts, faces, glass_ids = [], [], [] + for rad, y in sub_steps: + v = Vector((rad, 0, 0)) + for _ in range(sides): + verts.append((v[0], y, v[2])) + v.rotate(ang_step) + + JVWindows._loop_face_builder(len(sub_steps), sides, faces) + geometry_data.append((verts, faces, glass_ids)) + + # glass faces + verts, faces, glass_ids = geometry_data[-1] # pane entry + JVWindows._close_glass_faces_in_loops(len(verts), sides, faces, glass_ids) + + JVWindows._update_mesh_from_geometry_lists(mesh, geometry_data) + + @staticmethod + def _gothic(props, mesh): + geometry_data = [] + + width, height, jamb_w = props.window_width_medium, props.window_height_medium, props.jamb_width + res, frame_width, frame_th = props.window_resolution // 2, props.frame_width, props.frame_thickness + jamb_th = Units.INCH + + hframe_th, hjamb_w, hwidth, inset = frame_th / 2, jamb_w / 2, width / 2, Units.Q_INCH + vpl = 2 + (res + 2) + (res + 1) # two bottom, res + 2 on right side, res + 1 on left side + level_z = height - hwidth*sqrt(3) # the z value at which the arc begins (ignoring the thickness of the jamb) + + left_x = width + (2*jamb_th) + cx = left_x / 2 + steps = [ + ( # start xz start y end x which is also the radius + (jamb_th, -hjamb_w, jamb_th+width), + (0, -hjamb_w, width+(2*jamb_th)), + (0, hjamb_w, width+(2*jamb_th)), + (jamb_th, hjamb_w, jamb_th+width), + ), + ( + (jamb_th+frame_width, -hframe_th+inset, jamb_th+width-frame_width), + (jamb_th+frame_width, -hframe_th, jamb_th+width-frame_width), + (jamb_th, -hframe_th, jamb_th+width), + + (jamb_th, hframe_th, jamb_th+width), + (jamb_th+frame_width, hframe_th, jamb_th+width-frame_width), + (jamb_th+frame_width, hframe_th-inset, jamb_th+width-frame_width) + ) + ] + # the centers of rotation are located at 0 and left_x and are on the outside of the jambs + for substep in steps: + verts, faces, glass_ids = [], [], [] + + for sxz, sy, r in substep: + verts += [(sxz, sy, sxz), (r, sy, sxz)] # bottom two points + + # right side of arch + for x, z in JVWindows._gothic_arc_iterator(r, cx, res): + verts.append((x, sy, z+level_z)) + + iterator = iter(JVWindows._gothic_arc_iterator(r, cx, res, low_to_high=False)) + next(iterator) # skip highest entry as the previous loop already got it + for x, z in iterator: + verts.append((left_x-x, sy, z+level_z)) + + JVWindows._loop_face_builder(len(substep), vpl, faces) + geometry_data.append((verts, faces, glass_ids)) + + # glass faces + verts, faces, glass_ids = geometry_data[-1] # get last entry which will be the pane's geometry + JVWindows._close_glass_faces_in_loops(len(verts), vpl, faces, glass_ids) + + JVWindows._update_mesh_from_geometry_lists(mesh, geometry_data) + + @staticmethod + def _ellipse(props, mesh): + geometry_data = JVWindows._ellipse_worker(props.window_width_wide, props.window_height_short, + props.jamb_width, props.frame_width, props.window_resolution, + frame_th=props.frame_thickness) + + JVWindows._update_mesh_from_geometry_lists(mesh, geometry_data) + + @staticmethod + def _circular(props, mesh): + radius, res, jamb_w = props.window_radius, props.window_resolution, props.jamb_width + ang = props.window_angle + jamb_th, frame_width, frame_th, inset = Units.INCH, props.frame_width, props.frame_thickness, Units.Q_INCH + + hjamb_w, hframe_th = jamb_w / 2, frame_width / 2 + + if props.full_circle: + geometry_data = JVWindows._ellipse_worker(radius, radius, jamb_w, frame_width, res, + frame_th=frame_th, jamb_th=jamb_th, inset=inset) + else: + geometry_data = [] + start_angle, end_angle = ang - radians(180), -radians(180) # start angle from -X axis + hangle = (end_angle + start_angle) / 2 + + steps = ( + ( # radius, y value z value + (radius, -hjamb_w, jamb_th), (radius+jamb_th, -hjamb_w, 0), + (radius+jamb_th, hjamb_w, 0), (radius, hjamb_w, jamb_th) + ), + ( + (radius-frame_width, -hframe_th+inset, jamb_th+frame_width), + (radius-frame_width, -hframe_th, jamb_th+frame_width), + (radius, -hframe_th, jamb_th), + + (radius, hframe_th, jamb_th), + (radius-frame_width, hframe_th, jamb_th+frame_width), + (radius-frame_width, hframe_th-inset, jamb_th+frame_width), + ) + ) + + vpl = res + 3 + for substep in steps: + verts, faces, glass_ids = [], [], [] + for rad, y, z in substep: + # determine the location of the center vertex + cv = Vector((z / sin(abs(hangle)), 0, 0)) # adjust z so cv[2]=z and x ends up between whatever + cv.rotate(Euler((0, hangle, 0))) # rotate center vector to proper place + + verts.append((cv[0], y, cv[2])) + + # we have to clip the angles slightly because our center of radius is at (0, 0, 0), but we don't + # want to rotate all the way down to the axis for a point that is Z up from its + d_theta = asin(z / rad) + sa, ea = start_angle - d_theta, end_angle + d_theta + ang_step = Euler((0, (ea-sa) / (res + 1), 0)) + + v = Vector((rad, 0, 0)) + v.rotate(Euler((0, sa, 0))) + for _ in range(res + 2): # plus two for both end points + verts.append((v[0], y, v[2])) + v.rotate(ang_step) + + JVWindows._loop_face_builder(len(substep), vpl, faces) + geometry_data.append((verts, faces, glass_ids)) + + # glass ids + verts, faces, glass_ids = geometry_data[-1] + JVWindows._close_glass_faces_in_loops(len(verts), vpl, faces, glass_ids) + + JVWindows._update_mesh_from_geometry_lists(mesh, geometry_data) + + @staticmethod + def _bow(props, mesh): + jamb_w, frame_width, frame_th, jamb_th = props.jamb_width, props.frame_width, props.frame_thickness, Units.INCH + angle_per_pane, radius = radians(180) / props.bow_segments, props.window_width_wide / 2 + pane_start_angle = (radians(180) - angle_per_pane) / 2 # how much the pane is rotated from the radius + pane_width, height = 2 * radius * sin(angle_per_pane / 2), props.window_height_medium + inset = Units.Q_INCH + + hjw = (jamb_w * cos(radians(90) - pane_start_angle)) / 2 + pane_center_radius = radius + (frame_th / 2) + + geometry_data = [] + corner_vector, angle_step = Vector((-radius, 0, 0)), Euler((0, 0, -angle_per_pane)) + cur_pane_angle = pane_start_angle + + inner_jamb, outer_jamb = Vector((-pane_center_radius+hjw, 0, 0)), Vector((-pane_center_radius-hjw, 0, 0)) + + jamb_verts, jamb_faces = [], [] + # initial jamb vertices + for x in (-pane_center_radius+hjw, -pane_center_radius-hjw): + for z in (0, height + 2*jamb_th): + jamb_verts.append((x, -jamb_th, z)) + + for x in (-pane_center_radius+hjw, -pane_center_radius-hjw): + for z in (jamb_th, height + jamb_th): + jamb_verts.append((x, 0, z)) + + for i in range(props.bow_segments): + # create pane at +frame_th/2 so that the corner of the pane is at (0, 0, 0) + verts, faces, glass_ids = JVWindows._rectangular_pane_geometry(pane_width, height, (0, frame_th/2, jamb_th), + fw=frame_width, inset=inset, + frame_th=frame_th) + + JVWindows._transform_vertex_positions(verts, rotation=Euler((0, 0, cur_pane_angle)), + after_translation=corner_vector) + + geometry_data.append((verts, faces, glass_ids)) + + # vertical filler strips + geometry_data.append( + (*JVWindows._bay_bow_vertical_filler_strip(corner_vector, cur_pane_angle + radians(90), + angle_per_pane/2 if i == 0 else angle_per_pane, + height, frame_th, shift=(0, 0, jamb_th)), + [] + )) + + # jamb vertices + if i > 0: + for bz, tz in ((0, height + 2*jamb_th), (jamb_th, jamb_th+height)): + for x, y in (inner_jamb[:2], outer_jamb[:2]): + jamb_verts.append((x, y, bz)) + jamb_verts.append((x, y, tz)) + + # move to next pane + inner_jamb.rotate(angle_step) + outer_jamb.rotate(angle_step) + + corner_vector.rotate(angle_step) + cur_pane_angle -= angle_per_pane + + # last triangle filler + geometry_data.append( + (*JVWindows._bay_bow_vertical_filler_strip(corner_vector, 0, radians(90) - pane_start_angle, + height, frame_th, shift=(0, 0, jamb_th)), [])) + + # last set of jamb vertices + for x in (pane_center_radius-hjw, pane_center_radius+hjw): + for z in (0, height + 2*jamb_th): + jamb_verts.append((x, -jamb_th, z)) + + for x in (pane_center_radius-hjw, pane_center_radius+hjw): + for z in (jamb_th, height + jamb_th): + jamb_verts.append((x, 0, z)) + + # jamb faces + JVWindows._bay_bow_jamb_faces(len(jamb_verts), jamb_faces, props.bow_segments) + + geometry_data.append((jamb_verts, jamb_faces, [])) + JVWindows._update_mesh_from_geometry_lists(mesh, geometry_data) + + @staticmethod + def _bay(props, mesh): + width, height, pane_angle = props.window_width_wide, props.window_height_medium, props.bay_angle + depth = props.window_depth + jamb_w, jamb_th, frame_w, frame_th = props.jamb_width, Units.INCH, props.frame_width, props.frame_thickness + inset = Units.Q_INCH + + pane_x = depth / tan(pane_angle) + small_width, large_width = depth / sin(pane_angle), width - 2*pane_x + geometry_data = [] - if o.jv_is_bump: - layout.prop(o, "jv_norm_image", icon="TEXTURE") - layout.prop(o, "jv_bump_amo") + # panes + for angle, pw, corner in ( + (pane_angle, small_width, Vector((-width/2, 0, 0))), # left + (0, large_width, Vector((-large_width/2, depth, 0))), # middle + (-pane_angle, small_width, Vector((large_width/2, depth, 0))) # right + ): + verts, faces, glass_ids = JVWindows._rectangular_pane_geometry(pw, height, (0, frame_th/2, jamb_th), + frame_th=frame_th, fw=frame_w, inset=inset) + JVWindows._transform_vertex_positions(verts, Euler((0, 0, angle)), after_translation=corner) + geometry_data.append((verts, faces, glass_ids)) + + # end filler strips + geometry_data.extend(( + (*JVWindows._bay_bow_vertical_filler_strip(Vector((-width/2, 0, 0)), radians(180) - pane_angle/2, + pane_angle/2, height, frame_th, shift=(0, 0, jamb_th)), []), + (*JVWindows._bay_bow_vertical_filler_strip(Vector((width/2, 0, 0)), 0, pane_angle/2, + height, frame_th, shift=(0, 0, jamb_th)), []) + )) + + # in between + for x, angle in ((-large_width/2, radians(90)), (large_width/2, radians(90)-pane_angle)): + geometry_data.append(( + *JVWindows._bay_bow_vertical_filler_strip(Vector((x, depth, 0)), angle, pane_angle, height, frame_th, + shift=(0, 0, jamb_th)), [] + )) - layout.prop(o, "jv_im_scale") - layout.prop(o, "jv_is_rotate", icon="MAN_ROT") + # jamb + hjw = (jamb_w * cos(radians(90)-pane_angle)) / 2 + jamb_verts, jamb_faces = [], [] + cx = width/2 + frame_th/2 + + # starting jamb vertices + for x in (-cx+hjw, -cx-hjw): + for z in (0, height + 2*jamb_th): + jamb_verts.append((x, -jamb_th, z)) + + for x in (-cx+hjw, -cx-hjw): + for z in (jamb_th, height + jamb_th): + jamb_verts.append((x, 0, z)) + + # main jamb vertices + + for corner, inside_sign in ((Vector((-large_width/2, depth, 0)), -1), (Vector((large_width/2, depth, 0)), 1)): + inside = Vector((-inside_sign * (hjw - frame_th/2), 0, 0)) + outside = Vector((-inside_sign * (hjw+frame_th), 0, 0)) + + inside.rotate(Euler((0, 0, inside_sign * pane_angle/2))) + outside.rotate(Euler((0, 0, -inside_sign * (radians(90) + pane_angle/2)))) + + inside += corner + outside += corner + + for bz, tz in ((0, height + 2*jamb_th), (jamb_th, height+jamb_th)): + for x, y in (inside[:2], outside[:2]): + jamb_verts.append((x, y, bz)) + jamb_verts.append((x, y, tz)) + + # closing jamb vertices + for x in (cx-hjw, cx+hjw): + for z in (0, height + 2*jamb_th): + jamb_verts.append((x, -jamb_th, z)) + + for x in (cx-hjw, cx+hjw): + for z in (jamb_th, height + jamb_th): + jamb_verts.append((x, 0, z)) + + # jamb faces + JVWindows._bay_bow_jamb_faces(len(jamb_verts), jamb_faces, 3) + + geometry_data.append((jamb_verts, jamb_faces, [])) + JVWindows._update_mesh_from_geometry_lists(mesh, geometry_data) + + # WORKERS AND ITERATORS ------------------------------------------------------------------------------------- + @staticmethod + def _bay_bow_jamb_faces(vertex_count: int, faces: list, sides: int): + """ + Create the faces for the jamb on bay and bow windows + :param vertex_count: the number of vertices that jamb contains + :param faces: the list to add the faces to + :param sides: the number of sides the window has + """ + # jamb faces + p = 0 + for _ in range(sides): + faces.extend(( + (p, p+8, p+10, p+2), (p, p+8, p+12, p+4), (p+4, p+12, p+14, p+6), (p+2, p+6, p+14, p+10), + (p+5, p+13, p+15, p+7), (p+5, p+13, p+9, p+1), (p+1, p+9, p+11, p+3), (p+3, p+7, p+15, p+11) + )) + + p += 8 + + # vertical jamb faces + for p in (0, vertex_count-8): + faces.extend(( + (p, p+1, p+3, p+2), (p, p+4, p+5, p+1), (p+2, p+3, p+7, p+6), (p+4, p+5, p+7, p+6) + )) + + @staticmethod + def _bay_bow_vertical_filler_strip(corner_v: Vector, start_angle: float, angle: float, height: float, + frame_th: float, shift=(0, 0, 0)) -> Tuple[list, list]: + """ + Create the triangular filler strip found between bay and bow windows. Return vertices and faces. + :param corner_v: the location of the corner of the frame + :param start_angle: the angle, measured from the +X axis, with positive being CCW of corner_v + :param angle: how wide the angle is for the triangle + :param height: the height of the strip + :param frame_th: the thickness of the frame + :param shift: Amount to add to all x, y, and z positions + :return: the vertices and faces of the strip + """ + verts, faces = [], [] + dx, dy, dz = shift + + v1 = Vector((frame_th, 0, 0)) + v2 = v1.copy() + + v1.rotate(Euler((0, 0, start_angle))) + v2.rotate(Euler((0, 0, start_angle+angle))) + + for x, y in (corner_v[:2], (corner_v + v1)[:2], (corner_v + v2)[:2]): + verts.append((dx+x, dy+y, dz)) + verts.append((dx+x, dy+y, dz+height)) + + faces.extend(((0, 2, 3, 1), (2, 4, 5, 3), (4, 0, 1, 5), (1, 3, 5), (0, 4, 2))) + + return verts, faces + + @staticmethod + def _ellipse_worker(a, b, jamb_w, frame_width, res, frame_th=Units.INCH, jamb_th=Units.INCH, inset=Units.Q_INCH): + hjamb_w, hframe_th = jamb_w / 2, frame_th / 2 + res = res // 2 + vpl = res + 2 + res + + steps = ( + ( # ab_offset, y + (0, -hjamb_w), (jamb_th, -hjamb_w), + (jamb_th, hjamb_w), (0, hjamb_w) + ), + ( + (-frame_width, -hframe_th + inset), (-frame_width, -hframe_th), (0, -hframe_th), + (0, hframe_th), (-frame_width, hframe_th), (-frame_width, hframe_th - inset) + ) + ) + + geometry_data = [] + for substep in steps: + verts, faces, glass_ids = [], [], [] + + for ab_offset, yy in substep: + p = len(verts) + + # lower half + for x, z in JVWindows._ellipse_iterator(a + ab_offset, b + ab_offset, res): + verts.append((x, yy, z)) # negative because points are generate in CCW order, so flip side + + # top half + for x, y, z in verts[-2:p:-1]: # get all except end two points in reverse order + verts.append((x, y, -z)) + + JVWindows._loop_face_builder(len(substep), vpl, faces) + geometry_data.append((verts, faces, glass_ids)) + + verts, faces, glass_ids = geometry_data[-1] + JVWindows._close_glass_faces_in_loops(len(verts), vpl, faces, glass_ids) + + return geometry_data + + @staticmethod + def _rectangular_jamb_geometry(width: float, height: float, start_coord: tuple, jamb_width: float, keep_left=True, + th=Units.INCH) -> Tuple[list, list, list]: + """ + Create a rectangular jamb and return the vertex and face geometry + :param width: the width of the jamb (inside dimensions) + :param height: the height of the jamb (inside dimensions) + :param start_coord: the starting x, y, z coordinate + :param jamb_width: the width of the jamb + :param keep_left: whether or not to build the left side of the jamb + :return: the vertex, face, and glass id lists + """ + verts, faces, glass_ids = [], [], [] + hj = jamb_width / 2 + + if keep_left: + dx = th + else: + dx = 0 - layout.separator() - layout.operator("mesh.jv_window_materials", icon="MATERIAL") - layout.prop(o, "jv_is_preview", icon="SCENE") + x, y, z = start_coord + for yy in (y-hj, y+hj): + verts += [ # outer loop followed by inner loop + (x, yy, z), (x+th+width+th, yy, z), (x+th+width+th, yy, z+th+height+th), (x, yy, z+th+height+th), + (x+dx, yy, z+th), (x+th+width, yy, z+th), (x+th+width, yy, z+th+height), (x+dx, yy, z+th+height) + ] - # operators - layout.separator() - layout.separator() - layout.operator("mesh.jv_window_update", icon="FILE_REFRESH") - layout.operator("mesh.jv_window_delete", icon="CANCEL") - layout.operator("mesh.jv_window_add", icon="OUTLINER_OB_LATTICE") + # faces only front and back + for p in (0, 8): + for _ in range(3): + faces.append((p, p+1, p+5, p+4)) + p += 1 - else: - if context.object.jv_internal_type != "": - layout.label("This Is Already A JARCH Vis Object", icon="INFO") - layout.operator("mesh.jv_window_add", icon="OUTLINER_OB_LATTICE") - - -class WindowAdd(bpy.types.Operator): - bl_idname = "mesh.jv_window_add" - bl_label = "Add Window" - bl_description = "JARCH Vis: Window Generator" - - @classmethod - def poll(cls, context): - return context.mode == "OBJECT" - - def execute(self, context): - bpy.ops.mesh.primitive_cube_add() - o = context.object - o.jv_internal_type = "window" - o.jv_object_add = "add" - return {"FINISHED"} - - -class WindowUpdate(bpy.types.Operator): - bl_idname = "mesh.jv_window_update" - bl_label = "Update Window" - bl_options = {"UNDO", "INTERNAL"} - - def execute(self, context): - update_window(self, context) - return {"FINISHED"} - - -class WindowDelete(bpy.types.Operator): - bl_idname = "mesh.jv_window_delete" - bl_label = "Delete Window" - bl_options = {"UNDO", "INTERNAL"} - - def execute(self, context): - bpy.ops.object.delete() - return {"FINISHED"} - - -def register(): - bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() + if keep_left: + faces.append((p, p-3, p+1, p+4)) + + # outside faces + p = 0 + for _ in range(3): + faces.append((p, p+8, p+9, p+1)) + p += 1 + + # inside faces + p = 4 + for _ in range(3): + faces.append((p, p+8, p+9, p+1)) + p += 1 + + # outside and inside face on left + if keep_left: + faces.append((0, 8, 11, 3)) + faces.append((4, 12, 15, 7)) + + return verts, faces, glass_ids + + @staticmethod + def _rectangular_pane_geometry(w: float, h: float, start_coord: tuple, frame_th=1.5*Units.INCH, inset=Units.Q_INCH, + fw=Units.INCH) -> Tuple[list, list, list]: + """ + Create the geometry needed to build a rectangular window pane + :param w: the width of the pane + :param h: the height of the pane + :param start_coord: the start coordinate + :param frame_th: the thickness of the pane + :param inset: how much to inset when going from the frame to glass part of the pane + :param fw: thick the frame of the pane is + :return: + """ + verts, faces, glass_ids = [], [], [] + x, y, z = start_coord + fy, gy = frame_th / 2, (frame_th - (2*inset)) / 2 # half of the frame y and half of the glass y + + # start with inset-glass vertices then move out and around, ending at inset-glass vertices on the other side + verts += [ + (x+fw, y-gy, z+fw), (x+w-fw, y-gy, z+fw), (x+w-fw, y-gy, z+h-fw), (x+fw, y-gy, z+h-fw), + (x+fw, y-fy, z+fw), (x+w-fw, y-fy, z+fw), (x+w-fw, y-fy, z+h-fw), (x+fw, y-fy, z+h-fw), + (x, y-fy, z), (x+w, y-fy, z), (x+w, y-fy, z+h), (x, y-fy, z+h), + + (x, y+fy, z), (x+w, y+fy, z), (x+w, y+fy, z+h), (x, y+fy, z+h), + (x+fw, y+fy, z+fw), (x+w-fw, y+fy, z+fw), (x+w-fw, y+fy, z+h-fw), (x+fw, y+fy, z+h-fw), + (x+fw, y+gy, z+fw), (x+w-fw, y+gy, z+fw), (x+w-fw, y+gy, z+h-fw), (x+fw, y+gy, z+h-fw), + ] + + JVWindows._loop_face_builder(6, 4, faces) + faces.append((0, 1, 2, 3)) + p = len(verts) - 4 + faces.append((p, p+1, p+2, p+3)) + glass_ids.extend((len(faces) - 1, len(faces) - 2)) + + return verts, faces, glass_ids + + @staticmethod + def _ellipse_iterator(a, b, res) -> Tuple[float, float]: + """ + For an half-ellipse with x-axis radius of 'a' and y-axis radius of 'b' and the given resolution, return + the x, y coordinates of each point. Points are iterator in counter-clockwise fashion + :param a: the x-axis radius + :param b: the y-axis radius + :param res: (res + 2) points will be generated on the ellipse + :return: x, y points on the half-ellipse + """ + ang_step = radians(180) / (res + 1) + + theta = 0 + for _ in range(res + 2): # two extra for end points + x, y = a*cos(theta), b*sin(theta) + + yield (x, y) + theta += ang_step + + @staticmethod + def _gothic_arc_iterator(radius, cx, res, low_to_high=True) -> Tuple[float, float]: + """ + Generate (x, z) points on one side of a gothic arc with the given radius. + :param radius: the radius of the circle that forms the arc + :param cx: the x value of the center of the window + :param res: the number of segments on the arc, barring the start and end point + :param low_to_high: whether to start at the center and work outwards, or to start level and work to the top + :return a tuple of the (x, z) coordinates where x and z are the offset from the center of the arc + """ + # make angles negative because of how CCW rotation is negative in Blender + start_angle, end_angle = 0, -acos(cx / radius) + + if not low_to_high: # swap order if not low to high + start_angle, end_angle = end_angle, start_angle + + ang_step = Euler((0, (end_angle - start_angle) / (res + 1), 0)) + + v = Vector((radius, 0, 0)) + v.rotate(Euler((0, start_angle, 0))) + + for _ in range(res + 2): + yield (v[0], v[2]) # shift x to be centered around x=0 + + v.rotate(ang_step) + + @staticmethod + def _loop_face_builder(num_loops, vpl, faces): + """ + Generate faces assuming that the vertices are indexed in counter-clockwise loops such that L0 can be connected + to the corresponding vertices in L1. The last two loops have to be joined differently as their isn't the same + offset + :param num_loops: number of vertex loops + :param vpl: number of vertices per loop + :param faces: the list to add the faces to + """ + p = 0 + # first num_loops - 1 loops will follow same pattern + for _ in range(num_loops-1): + for _ in range(vpl-1): # last face in loop has to be manually connected + faces.append((p, p+vpl, p+vpl+1, p+1)) + p += 1 + faces.append((p, p+vpl, p+1, p+1-vpl)) # last face in loop + p += 1 + + # connect 4th loop to 1st loop + p, e = 0, (num_loops-1) * vpl # first vertex index and index of vertex at the start of the last loop + for _ in range(vpl-1): + faces.append((p, e, e+1, p+1)) + e += 1 + p += 1 + faces.append((p, e, e+1-vpl, p+1-vpl)) # last face + + @staticmethod + def _close_glass_faces_in_loops(v_len: int, vpl: int, faces: list, glass_ids: list): + """ + Many of the windows are built by creating the jamb then the pane with sequential vertex loops. There will be + six loops for the pane, with the first loop forming the inside piece of glass, and the last loop forming the + outside piece of glass. Calculate those faces. + :param v_len: The number of vertices currently created + :param vpl: The number of vertices per vertex loop + :param faces: the list of faces + :param glass_ids: the list of face indices that need to have the glass material + """ + p = v_len - (6 * vpl) # go back to start of first pane loop + faces.append([i for i in range(p, p + vpl)]) + p = v_len - vpl + faces.append([i for i in range(p, p + vpl)]) + + glass_ids.extend((len(faces) - 1, len(faces) - 2))