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))