Bevel: Custom Profile and CurveProfile Widget
authorHans Goudey <h.goudey@me.com>
Wed, 20 Nov 2019 21:12:32 +0000 (16:12 -0500)
committerHans Goudey <h.goudey@me.com>
Wed, 20 Nov 2019 21:25:28 +0000 (16:25 -0500)
Custom profiles in bevel allows the profile curve to be controlled by
manually placed control points. Orientation is regularized along
groups of edges, and the 'pipe case' is updated. This commit includes
many updates to comments and changed variable names as well.

A 'cutoff' vertex mesh method is added to bevel in addition to the
existing grid fill option for replacing vertices.

The UI of the bevel modifier and tool are updated and unified.

Also, a 'CurveProfile' widget is added to BKE for defining the profile
in the interface, which may be useful in other situations.

Many thanks to Howard, my mentor for this GSoC project.

Reviewers: howardt, campbellbarton

Differential Revision: https://developer.blender.org/D5516

39 files changed:
release/scripts/presets/keyconfig/keymap_data/blender_default.py
release/scripts/startup/bl_ui/properties_data_modifier.py
source/blender/CMakeLists.txt
source/blender/blenkernel/BKE_curveprofile.h [new file with mode: 0644]
source/blender/blenkernel/CMakeLists.txt
source/blender/blenkernel/intern/curveprofile.c [new file with mode: 0644]
source/blender/blenkernel/intern/scene.c
source/blender/blenloader/intern/readfile.c
source/blender/blenloader/intern/versioning_280.c
source/blender/blenloader/intern/versioning_defaults.c
source/blender/blenloader/intern/writefile.c
source/blender/bmesh/intern/bmesh_opdefines.c
source/blender/bmesh/intern/bmesh_operator_api.h
source/blender/bmesh/intern/bmesh_operators.h
source/blender/bmesh/operators/bmo_bevel.c
source/blender/bmesh/tools/bmesh_bevel.c
source/blender/bmesh/tools/bmesh_bevel.h
source/blender/editors/include/UI_interface.h
source/blender/editors/interface/interface_draw.c
source/blender/editors/interface/interface_handlers.c
source/blender/editors/interface/interface_intern.h
source/blender/editors/interface/interface_query.c
source/blender/editors/interface/interface_templates.c
source/blender/editors/interface/interface_widgets.c
source/blender/editors/mesh/editmesh_bevel.c
source/blender/makesdna/DNA_curveprofile_types.h [new file with mode: 0644]
source/blender/makesdna/DNA_modifier_types.h
source/blender/makesdna/DNA_scene_types.h
source/blender/makesdna/intern/makesdna.c
source/blender/makesrna/RNA_access.h
source/blender/makesrna/RNA_enum_types.h
source/blender/makesrna/intern/CMakeLists.txt
source/blender/makesrna/intern/makesrna.c
source/blender/makesrna/intern/rna_curveprofile.c [new file with mode: 0644]
source/blender/makesrna/intern/rna_internal.h
source/blender/makesrna/intern/rna_modifier.c
source/blender/makesrna/intern/rna_scene.c
source/blender/makesrna/intern/rna_ui_api.c
source/blender/modifiers/intern/MOD_bevel.c

index b2ce3c8e608b7ebd7a953d3a774f6fd671178063..f86b017806f6dcf762e838626e0dba8009c723c3 100644 (file)
@@ -4680,6 +4680,8 @@ def km_bevel_modal_map(_params):
         ("MARK_SHARP_TOGGLE", {"type": 'K', "value": 'PRESS', "any": True}, None),
         ("OUTER_MITER_CHANGE", {"type": 'O', "value": 'PRESS', "any": True}, None),
         ("INNER_MITER_CHANGE", {"type": 'I', "value": 'PRESS', "any": True}, None),
+        ("CUSTOM_PROFILE_TOGGLE", {"type": 'Z', "value": 'PRESS', "any": True}, None),
+        ("VERTEX_MESH_CHANGE", {"type": 'N', "value": 'PRESS', "any": True}, None),
     ])
 
     return keymap
index 05e8f0fce38cbcb3a85c9eed0b48ddba9cd082bd..f576a29a783897c45d6f4f933e0f422d257e33dc 100644 (file)
@@ -133,43 +133,55 @@ class DATA_PT_modifiers(ModifierButtonsPanel, Panel):
         layout.prop(md, "end_cap")
 
     def BEVEL(self, layout, ob, md):
-        split = layout.split()
-
-        col = split.column()
-        if md.offset_type == 'PERCENT':
-            col.prop(md, "width_pct")
+        offset_type = md.offset_type
+        if offset_type == 'PERCENT':
+            layout.prop(md, "width_pct")
         else:
-            col.prop(md, "width")
-        col.prop(md, "segments")
-        col.prop(md, "profile")
-        col.prop(md, "material")
+            offset_text = "Width"
+            if offset_type == 'DEPTH':
+                offset_text = "Depth"
+            elif offset_type == 'OFFSET':
+                offset_text = "Offset"
+            layout.prop(md, "width", text=offset_text)
+        layout.row().prop(md, "offset_type", expand=True)
 
+        split = layout.split()
         col = split.column()
         col.prop(md, "use_only_vertices")
         col.prop(md, "use_clamp_overlap")
         col.prop(md, "loop_slide")
+        col = split.column()
         col.prop(md, "mark_seam")
         col.prop(md, "mark_sharp")
         col.prop(md, "harden_normals")
 
+        layout.row().prop(md, "segments")
+        layout.row().prop(md, "profile")
+        layout.row().prop(md, "material")
+
+        layout.label(text="Miter Type:")
+        layout.row().prop(md, "miter_outer", text="Outer")
+        layout.row().prop(md, "miter_inner", text="Inner")
+        if md.miter_inner in {'MITER_PATCH', 'MITER_ARC'}:
+            layout.row().prop(md, "spread")
+
         layout.label(text="Limit Method:")
         layout.row().prop(md, "limit_method", expand=True)
         if md.limit_method == 'ANGLE':
             layout.prop(md, "angle_limit")
         elif md.limit_method == 'VGROUP':
-            layout.label(text="Vertex Group:")
             layout.prop_search(md, "vertex_group", ob, "vertex_groups", text="")
 
-        layout.label(text="Width Method:")
-        layout.row().prop(md, "offset_type", expand=True)
-
-        layout.label(text="Set Face Strength Mode")
+        layout.label(text="Face Strength Mode:")
         layout.row().prop(md, "face_strength_mode", expand=True)
 
-        layout.label(text="Miter Patterns")
-        layout.row().prop(md, "miter_outer")
-        layout.row().prop(md, "miter_inner")
-        layout.row().prop(md, "spread")
+        layout.label(text="Intersection Type:")
+        layout.row().prop(md, "vmesh_method", expand=True)
+        layout.row().prop(md, "use_custom_profile")
+        row = layout.row()
+        row.enabled = md.use_custom_profile
+        if md.use_custom_profile:
+            layout.template_curveprofile(md, "custom_profile")
 
     def BOOLEAN(self, layout, _ob, md):
         split = layout.split()
index 203543b0ef09735e9210d1a7dbd7f69c17eab7a2..62923d18b7044a7ae721de528a3face7372d241a 100644 (file)
@@ -68,6 +68,7 @@ set(SRC_DNA_INC
   ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_outliner_types.h
   ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_packedFile_types.h
   ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_particle_types.h
+  ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_curveprofile_types.h
   ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_rigidbody_types.h
   ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_scene_types.h
   ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_screen_types.h
diff --git a/source/blender/blenkernel/BKE_curveprofile.h b/source/blender/blenkernel/BKE_curveprofile.h
new file mode 100644 (file)
index 0000000..1f6659d
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * 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 2
+ * 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, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright (C) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+
+#ifndef __BKE_CURVEPROFILE_H__
+#define __BKE_CURVEPROFILE_H__
+
+/** \file
+ * \ingroup bke
+ */
+
+struct CurveProfile;
+struct CurveProfilePoint;
+
+void BKE_curveprofile_set_defaults(struct CurveProfile *profile);
+
+struct CurveProfile *BKE_curveprofile_add(int preset);
+
+void BKE_curveprofile_free_data(struct CurveProfile *profile);
+
+void BKE_curveprofile_free(struct CurveProfile *profile);
+
+void BKE_curveprofile_copy_data(struct CurveProfile *target, const struct CurveProfile *profile);
+
+struct CurveProfile *BKE_curveprofile_copy(const struct CurveProfile *profile);
+
+bool BKE_curveprofile_remove_point(struct CurveProfile *profile, struct CurveProfilePoint *point);
+
+void BKE_curveprofile_remove_by_flag(struct CurveProfile *profile, const short flag);
+
+struct CurveProfilePoint *BKE_curveprofile_insert(struct CurveProfile *profile, float x, float y);
+
+void BKE_curveprofile_selected_handle_set(struct CurveProfile *profile, int type_1, int type_2);
+
+void BKE_curveprofile_reverse(struct CurveProfile *profile);
+
+void BKE_curveprofile_reset(struct CurveProfile *profile);
+
+void BKE_curveprofile_create_samples(struct CurveProfile *profile,
+                                     int segments_len,
+                                     bool sample_straight_edges,
+                                     struct CurveProfilePoint *r_samples);
+
+void BKE_curveprofile_initialize(struct CurveProfile *profile, short segments_len);
+
+/* Called for a complete update of the widget after modifications */
+void BKE_curveprofile_update(struct CurveProfile *profile, const bool rem_doubles);
+
+/* Need to find the total length of the curve to sample a portion of it */
+float BKE_curveprofile_total_length(const struct CurveProfile *profile);
+
+void BKE_curveprofile_create_samples_even_spacing(struct CurveProfile *profile,
+                                                   int segments_len,
+                                                   struct CurveProfilePoint *r_samples);
+
+/* Length portion is the fraction of the total path length where we want the location */
+void BKE_curveprofile_evaluate_length_portion(const struct CurveProfile *profile,
+                                              float length_portion,
+                                              float *x_out,
+                                              float *y_out);
+#endif
index 023980292fa3387ee0d072548e3abbeb71e73e08..49cead5c003a4bf4df4d17ee7f933087d5d27064 100644 (file)
@@ -185,6 +185,7 @@ set(SRC
   intern/pbvh_bmesh.c
   intern/pbvh_parallel.cc
   intern/pointcache.c
+  intern/curveprofile.c
   intern/report.c
   intern/rigidbody.c
   intern/scene.c
@@ -330,6 +331,7 @@ set(SRC
   BKE_particle.h
   BKE_pbvh.h
   BKE_pointcache.h
+  BKE_curveprofile.h
   BKE_report.h
   BKE_rigidbody.h
   BKE_scene.h
diff --git a/source/blender/blenkernel/intern/curveprofile.c b/source/blender/blenkernel/intern/curveprofile.c
new file mode 100644 (file)
index 0000000..6689eca
--- /dev/null
@@ -0,0 +1,1011 @@
+/*
+ * 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 2
+ * 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, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright (C) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup bke
+ */
+
+#include <string.h>
+#include <math.h>
+#include <stdlib.h>
+#include <float.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "DNA_curveprofile_types.h"
+#include "DNA_curve_types.h"
+
+#include "BLI_blenlib.h"
+#include "BLI_math.h"
+#include "BLI_utildefines.h"
+#include "BLI_task.h"
+#include "BLI_threads.h"
+
+#include "BKE_curveprofile.h"
+#include "BKE_curve.h"
+#include "BKE_fcurve.h"
+
+void BKE_curveprofile_free_data(CurveProfile *profile)
+{
+  MEM_SAFE_FREE(profile->path);
+  MEM_SAFE_FREE(profile->table);
+  MEM_SAFE_FREE(profile->segments);
+}
+
+void BKE_curveprofile_free(CurveProfile *profile)
+{
+  if (profile) {
+    BKE_curveprofile_free_data(profile);
+    MEM_freeN(profile);
+  }
+}
+
+void BKE_curveprofile_copy_data(CurveProfile *target, const CurveProfile *profile)
+{
+  *target = *profile;
+
+  target->path = MEM_dupallocN(profile->path);
+  target->table = MEM_dupallocN(profile->table);
+  target->segments = MEM_dupallocN(profile->segments);
+}
+
+CurveProfile *BKE_curveprofile_copy(const CurveProfile *profile)
+{
+  if (profile) {
+    CurveProfile *new_prdgt = MEM_dupallocN(profile);
+    BKE_curveprofile_copy_data(new_prdgt, profile);
+    return new_prdgt;
+  }
+  return NULL;
+}
+
+/** Removes a specific point from the path of control points.
+ * \note: Requires curveprofile_update call after. */
+bool BKE_curveprofile_remove_point(CurveProfile *profile, CurveProfilePoint *point)
+{
+  CurveProfilePoint *pts;
+
+  /* Must have 2 points minimum. */
+  if (profile->path_len <= 2) {
+    return false;
+  }
+
+  /* Input point must be within the array. */
+  if (!(point > profile->path && point < profile->path + profile->path_len)) {
+    return false;
+  }
+
+  pts = MEM_mallocN(sizeof(CurveProfilePoint) * profile->path_len, "path points");
+
+  uint i_delete = (uint)(point - profile->path);
+
+  /* Copy the before and after the deleted point. */
+  memcpy(pts, profile->path, i_delete);
+  memcpy(pts + i_delete, profile->path + i_delete + 1, (size_t)profile->path_len - i_delete - 1);
+
+  MEM_freeN(profile->path);
+  profile->path = pts;
+  profile->path_len -= 1;
+  return true;
+}
+
+/** Removes every point in the widget with the supplied flag set, except for the first and last.
+ * \param flag: CurveProfilePoint->flag.
+ * \note: Requires curveprofile_update call after. */
+void BKE_curveprofile_remove_by_flag(CurveProfile *profile, const short flag)
+{
+  int i_old, i_new, n_removed = 0;
+
+  /* Copy every point without the flag into the new path. */
+  CurveProfilePoint *new_pts = MEM_mallocN(sizeof(CurveProfilePoint) * profile->path_len,
+                                           "profile path");
+
+  /* Build the new list without any of the points with the flag. Keep the first and last points. */
+  new_pts[0] = profile->path[0];
+  for (i_old = 1, i_new = 1; i_old < profile->path_len - 1; i_old++) {
+    if (!(profile->path[i_old].flag & flag)) {
+      new_pts[i_new] = profile->path[i_old];
+      i_new++;
+    }
+    else {
+      n_removed++;
+    }
+  }
+  new_pts[i_new] = profile->path[i_old];
+
+  MEM_freeN(profile->path);
+  profile->path = new_pts;
+  profile->path_len -= n_removed;
+}
+
+/** Adds a new point at the specified location. The choice for which points to place the new vertex
+ * between is made by checking which control point line segment is closest to the new point and
+ * placing the new vertex in between that segment's points.
+ * \note: Requires curveprofile_update call after. */
+CurveProfilePoint *BKE_curveprofile_insert(CurveProfile *profile, float x, float y)
+{
+  CurveProfilePoint *new_pt = NULL;
+  float new_loc[2] = {x, y};
+
+  /* Don't add more control points  than the maximum size of the higher resolution table. */
+  if (profile->path_len == PROF_TABLE_MAX - 1) {
+    return NULL;
+  }
+
+  /* Find the index at the line segment that's closest to the new position. */
+  float distance;
+  float min_distance = FLT_MAX;
+  int i_insert = 0;
+  for (int i = 0; i < profile->path_len - 1; i++) {
+    float loc1[2] = {profile->path[i].x, profile->path[i].y};
+    float loc2[2] = {profile->path[i + 1].x, profile->path[i + 1].y};
+
+    distance = dist_squared_to_line_segment_v2(new_loc, loc1, loc2);
+    if (distance < min_distance) {
+      min_distance = distance;
+      i_insert = i + 1;
+    }
+  }
+
+  /* Insert the new point at the location we found and copy all of the old points in as well. */
+  profile->path_len++;
+  CurveProfilePoint *new_pts = MEM_mallocN(sizeof(CurveProfilePoint) * profile->path_len,
+                                           "profile path");
+  for (int i_new = 0, i_old = 0; i_new < profile->path_len; i_new++) {
+    if (i_new != i_insert) {
+      /* Insert old points */
+      new_pts[i_new].x = profile->path[i_old].x;
+      new_pts[i_new].y = profile->path[i_old].y;
+      new_pts[i_new].flag = profile->path[i_old].flag & ~PROF_SELECT; /* Deselect old points. */
+      new_pts[i_new].h1 = profile->path[i_old].h1;
+      new_pts[i_new].h2 = profile->path[i_old].h2;
+      i_old++;
+    }
+    else {
+      /* Insert new point. */
+      new_pts[i_new].x = x;
+      new_pts[i_new].y = y;
+      new_pts[i_new].flag = PROF_SELECT;
+      new_pt = &new_pts[i_new];
+      /* Set handles of new point based on its neighbors. */
+      if (new_pts[i_new - 1].h2 == HD_VECT && profile->path[i_insert].h1 == HD_VECT) {
+        new_pt->h1 = new_pt->h2 = HD_VECT;
+      }
+      else {
+        new_pt->h1 = new_pt->h2 = HD_AUTO;
+      }
+    }
+  }
+
+  /* Free the old path and use the new one. */
+  MEM_freeN(profile->path);
+  profile->path = new_pts;
+  return new_pt;
+}
+
+/** Sets the handle type of the selected control points.
+ * \param type_*: Either HD_VECT or HD_AUTO. Handle types for the first and second handles.
+ * \note: Requires curveprofile_update call after. */
+void BKE_curveprofile_selected_handle_set(CurveProfile *profile, int type_1, int type_2)
+{
+  for (int i = 0; i < profile->path_len; i++) {
+    if (profile->path[i].flag & PROF_SELECT) {
+      switch (type_1) {
+        case HD_AUTO:
+          profile->path[i].h1 = HD_AUTO;
+          break;
+        case HD_VECT:
+          profile->path[i].h1 = HD_VECT;
+          break;
+        default:
+          profile->path[i].h1 = HD_AUTO;
+          break;
+      }
+      switch (type_2) {
+        case HD_AUTO:
+          profile->path[i].h2 = HD_AUTO;
+          break;
+        case HD_VECT:
+          profile->path[i].h2 = HD_VECT;
+          break;
+      default:
+        profile->path[i].h1 = HD_AUTO;
+        break;
+      }
+    }
+  }
+}
+
+/** Flips the profile across the diagonal so that its orientation is reversed.
+ * \note: Requires curveprofile_update call after.  */
+void BKE_curveprofile_reverse(CurveProfile *profile)
+{
+  /* When there are only two points, reversing shouldn't do anything. */
+  if (profile->path_len == 2) {
+    return;
+  }
+  CurveProfilePoint *new_pts = MEM_mallocN(sizeof(CurveProfilePoint) * profile->path_len,
+                                           "profile path");
+  /* Mirror the new points across the y = x line */
+  for (int i = 0; i < profile->path_len; i++) {
+    new_pts[profile->path_len - i - 1].x = profile->path[i].y;
+    new_pts[profile->path_len - i - 1].y = profile->path[i].x;
+    new_pts[profile->path_len - i - 1].flag = profile->path[i].flag;
+    new_pts[profile->path_len - i - 1].h1 = profile->path[i].h1;
+    new_pts[profile->path_len - i - 1].h2 = profile->path[i].h2;
+  }
+
+  /* Free the old points and use the new ones */
+  MEM_freeN(profile->path);
+  profile->path = new_pts;
+}
+
+/** Builds a quarter circle profile with space on each side for 'support loops.' */
+static void CurveProfile_build_supports(CurveProfile *profile)
+{
+  int n = profile->path_len;
+
+  profile->path[0].x = 1.0;
+  profile->path[0].y = 0.0;
+  profile->path[0].flag = 0;
+  profile->path[0].h1 = HD_VECT;
+  profile->path[0].h2 = HD_VECT;
+  profile->path[1].x = 1.0;
+  profile->path[1].y = 0.5;
+  profile->path[1].flag = 0;
+  profile->path[1].h1 = HD_VECT;
+  profile->path[1].h2 = HD_VECT;
+  for (int i = 1; i < n - 2; i++) {
+    profile->path[i + 1].x = 1.0f - (0.5f * (1.0f - cosf((float)((i / (float)(n - 3))) * M_PI_2)));
+    profile->path[i + 1].y = 0.5f + 0.5f * sinf((float)((i / (float)(n - 3)) * M_PI_2));
+    profile->path[i + 1].flag = 0;
+    profile->path[i + 1].h1 = HD_AUTO;
+    profile->path[i + 1].h2 = HD_AUTO;
+  }
+  profile->path[n - 2].x = 0.5;
+  profile->path[n - 2].y = 1.0;
+  profile->path[n - 2].flag = 0;
+  profile->path[n - 2].h1 = HD_VECT;
+  profile->path[n - 2].h2 = HD_VECT;
+  profile->path[n - 1].x = 0.0;
+  profile->path[n - 1].y = 1.0;
+  profile->path[n - 1].flag = 0;
+  profile->path[n - 1].h1 = HD_VECT;
+  profile->path[n - 1].h2 = HD_VECT;
+}
+
+/** Puts the widget's control points in a step pattern. Uses vector handles for each point. */
+static void CurveProfile_build_steps(CurveProfile *profile)
+{
+  int n, step_x, step_y;
+  float n_steps_x, n_steps_y;
+
+  n = profile->path_len;
+
+  /* Special case for two points to avoid dividing by zero later. */
+  if (n == 2) {
+    profile->path[0].x = 1.0f;
+    profile->path[0].y = 0.0f;
+    profile->path[0].flag = 0;
+    profile->path[0].h1 = HD_VECT;
+    profile->path[0].h2 = HD_VECT;
+    profile->path[1].x = 0.0f;
+    profile->path[1].y = 1.0f;
+    profile->path[1].flag = 0;
+    profile->path[1].h1 = HD_VECT;
+    profile->path[1].h2 = HD_VECT;
+    return;
+  }
+
+  n_steps_x = (n % 2 == 0) ? n : (n - 1);
+  n_steps_y = (n % 2 == 0) ? (n - 2) : (n - 1);
+
+  for (int i = 0; i < n; i++) {
+    step_x = (i + 1) / 2;
+    step_y = i / 2;
+    profile->path[i].x = 1.0f - ((float)(2 * step_x) / n_steps_x);
+    profile->path[i].y = (float)(2 * step_y) / n_steps_y;
+    profile->path[i].flag = 0;
+    profile->path[i].h1 = HD_VECT;
+    profile->path[i].h2 = HD_VECT;
+  }
+}
+
+/** Shorthand helper function for setting location and interpolation of a point. */
+static void point_init(CurveProfilePoint *point, float x, float y, short flag, char h1, char h2)
+{
+  point->x = x;
+  point->y = y;
+  point->flag = flag;
+  point->h1 = h1;
+  point->h2 = h2;
+}
+
+/** Resets the profile to the current preset.
+ * \note: Requires curveprofile_update call after. */
+void BKE_curveprofile_reset(CurveProfile *profile)
+{
+  if (profile->path) {
+    MEM_freeN(profile->path);
+  }
+
+  int preset = profile->preset;
+  switch (preset) {
+    case PROF_PRESET_LINE:
+      profile->path_len = 2;
+      break;
+    case PROF_PRESET_SUPPORTS:
+      /* Use a dynamic number of control points for the widget's profile. */
+      if (profile->segments_len < 4) {
+        /* But always use enough points to at least build the support points. */
+        profile->path_len = 5;
+      }
+      else {
+        profile->path_len = profile->segments_len + 1;
+      }
+      break;
+    case PROF_PRESET_CORNICE:
+      profile->path_len = 13;
+      break;
+    case PROF_PRESET_CROWN:
+      profile->path_len = 11;
+      break;
+    case PROF_PRESET_STEPS:
+      /* Also use dynamic number of control points based on the set number of segments. */
+      if (profile->segments_len == 0) {
+        /* totsegments hasn't been set-- use the number of control points for 8 steps. */
+        profile->path_len = 17;
+      }
+      else {
+        profile->path_len = profile->segments_len + 1;
+      }
+      break;
+  }
+
+  profile->path = MEM_callocN(sizeof(CurveProfilePoint) * profile->path_len, "profile path");
+
+  switch (preset) {
+    case PROF_PRESET_LINE:
+      point_init(&profile->path[0], 1.0f, 0.0f, 0, HD_AUTO, HD_AUTO);
+      point_init(&profile->path[1], 0.0f, 1.0f, 0, HD_AUTO, HD_AUTO);
+      break;
+    case PROF_PRESET_SUPPORTS:
+      CurveProfile_build_supports(profile);
+      break;
+    case PROF_PRESET_CORNICE:
+      point_init(&profile->path[0], 1.0f, 0.0f, 0, HD_VECT, HD_VECT);
+      point_init(&profile->path[1], 1.0f, 0.125f, 0, HD_VECT, HD_VECT);
+      point_init(&profile->path[2], 0.92f, 0.16f, 0, HD_AUTO, HD_AUTO);
+      point_init(&profile->path[3], 0.875f, 0.25f, 0, HD_VECT, HD_VECT);
+      point_init(&profile->path[4], 0.8f, 0.25f, 0, HD_VECT, HD_VECT);
+      point_init(&profile->path[5], 0.733f, 0.433f, 0, HD_AUTO, HD_AUTO);
+      point_init(&profile->path[6], 0.582f, 0.522f, 0, HD_AUTO, HD_AUTO);
+      point_init(&profile->path[7], 0.4f, 0.6f, 0, HD_AUTO, HD_AUTO);
+      point_init(&profile->path[8], 0.289f, 0.727f, 0, HD_AUTO, HD_AUTO);
+      point_init(&profile->path[9], 0.25f, 0.925f, 0, HD_VECT, HD_VECT);
+      point_init(&profile->path[10], 0.175f, 0.925f, 0, HD_VECT, HD_VECT);
+      point_init(&profile->path[11], 0.175f, 1.0f, 0, HD_VECT, HD_VECT);
+      point_init(&profile->path[12], 0.0f, 1.0f, 0, HD_VECT, HD_VECT);
+      break;
+    case PROF_PRESET_CROWN:
+      point_init(&profile->path[0], 1.0f, 0.0f, 0, HD_VECT, HD_VECT);
+      point_init(&profile->path[1], 1.0f, 0.25f, 0, HD_VECT, HD_VECT);
+      point_init(&profile->path[2], 0.75f, 0.25f, 0, HD_VECT, HD_VECT);
+      point_init(&profile->path[3], 0.75f, 0.325f, 0, HD_VECT, HD_VECT);
+      point_init(&profile->path[4], 0.925f, 0.4f, 0, HD_AUTO, HD_AUTO);
+      point_init(&profile->path[5], 0.975f, 0.5f, 0, HD_AUTO, HD_AUTO);
+      point_init(&profile->path[6], 0.94f, 0.65f, 0, HD_AUTO, HD_AUTO);
+      point_init(&profile->path[7], 0.85f, 0.75f, 0, HD_AUTO, HD_AUTO);
+      point_init(&profile->path[8], 0.75f, 0.875f, 0, HD_AUTO, HD_AUTO);
+      point_init(&profile->path[9], 0.7f, 1.0f, 0, HD_VECT, HD_VECT);
+      point_init(&profile->path[10], 0.0f, 1.0f, 0, HD_VECT, HD_VECT);
+      break;
+    case PROF_PRESET_STEPS:
+      CurveProfile_build_steps(profile);
+      break;
+  }
+
+  if (profile->table) {
+    MEM_freeN(profile->table);
+    profile->table = NULL;
+  }
+}
+
+/** Helper for 'curve_profile_create' samples. Returns whether both handles that make up the edge
+ * are vector handles. */
+static bool is_curved_edge(BezTriple *bezt, int i)
+{
+  return (bezt[i].h2 != HD_VECT || bezt[i + 1].h1 != HD_VECT);
+}
+
+/** Used to set bezier handle locations in the sample creation process. Reduced copy of
+ * #calchandleNurb_intern code in curve.c. */
+static void calchandle_profile(BezTriple *bezt, const BezTriple *prev, const BezTriple *next)
+{
+#define point_handle1 ((point_loc)-3)
+#define point_handle2 ((point_loc) + 3)
+
+  const float *prev_loc, *next_loc;
+  float *point_loc;
+  float pt[3];
+  float len, len_a, len_b;
+  float dvec_a[2], dvec_b[2];
+
+  if (bezt->h1 == 0 && bezt->h2 == 0) {
+    return;
+  }
+
+  point_loc = bezt->vec[1];
+
+  if (prev == NULL) {
+    next_loc = next->vec[1];
+    pt[0] = 2.0f * point_loc[0] - next_loc[0];
+    pt[1] = 2.0f * point_loc[1] - next_loc[1];
+    prev_loc = pt;
+  }
+  else {
+    prev_loc = prev->vec[1];
+  }
+
+  if (next == NULL) {
+    prev_loc = prev->vec[1];
+    pt[0] = 2.0f * point_loc[0] - prev_loc[0];
+    pt[1] = 2.0f * point_loc[1] - prev_loc[1];
+    next_loc = pt;
+  }
+  else {
+    next_loc = next->vec[1];
+  }
+
+  sub_v2_v2v2(dvec_a, point_loc, prev_loc);
+  sub_v2_v2v2(dvec_b, next_loc, point_loc);
+
+  len_a = len_v2(dvec_a);
+  len_b = len_v2(dvec_b);
+
+  if (len_a == 0.0f) {
+    len_a = 1.0f;
+  }
+  if (len_b == 0.0f) {
+    len_b = 1.0f;
+  }
+
+  if (bezt->h1 == HD_AUTO || bezt->h2 == HD_AUTO) { /* auto */
+    float tvec[2];
+    tvec[0] = dvec_b[0] / len_b + dvec_a[0] / len_a;
+    tvec[1] = dvec_b[1] / len_b + dvec_a[1] / len_a;
+
+    len = len_v2(tvec) * 2.5614f;
+    if (len != 0.0f) {
+
+      if (bezt->h1 == HD_AUTO) {
+        len_a /= len;
+        madd_v2_v2v2fl(point_handle1, point_loc, tvec, -len_a);
+      }
+      if (bezt->h2 == HD_AUTO) {
+        len_b /= len;
+        madd_v2_v2v2fl(point_handle2, point_loc, tvec, len_b);
+      }
+    }
+  }
+
+  if (bezt->h1 == HD_VECT) { /* vector */
+    madd_v2_v2v2fl(point_handle1, point_loc, dvec_a, -1.0f / 3.0f);
+  }
+  if (bezt->h2 == HD_VECT) {
+    madd_v2_v2v2fl(point_handle2, point_loc, dvec_b, 1.0f / 3.0f);
+  }
+#undef point_handle1
+#undef point_handle2
+}
+
+/** Helper function for 'BKE_CurveProfile_create_samples.' Calculates the angle between the
+ * handles on the inside of the edge starting at index i. A larger angle means the edge is
+ * more curved.
+ * \param i_edge: The start index of the edge to calculate the angle for. */
+static float bezt_edge_handle_angle(const BezTriple *bezt, int i_edge)
+{
+  /* Find the direction of the handles that define this edge along the direction of the path. */
+  float start_handle_direction[2], end_handle_direction[2];
+  /* Handle 2 - point location. */
+  sub_v2_v2v2(start_handle_direction, bezt[i_edge].vec[2], bezt[i_edge].vec[1]);
+  /* Point location - handle 1. */
+  sub_v2_v2v2(end_handle_direction, bezt[i_edge + 1].vec[1], bezt[i_edge + 1].vec[0]);
+
+  float angle = angle_v2v2(start_handle_direction, end_handle_direction);
+  return angle;
+}
+
+/** Struct to sort curvature of control point edges. */
+typedef struct {
+  /** The index of the corresponding bezier point. */
+  int bezt_index;
+  /** The curvature of the edge with the above index. */
+  float bezt_curvature;
+} CurvatureSortPoint;
+
+/** Helper function for 'BKE_curveprofile_create_samples' for sorting edges based on curvature. */
+static int sort_points_curvature(const void *in_a, const void *in_b)
+{
+  const CurvatureSortPoint *a = (const CurvatureSortPoint *)in_a;
+  const CurvatureSortPoint *b = (const CurvatureSortPoint *)in_b;
+
+  if (a->bezt_curvature > b->bezt_curvature) {
+    return 0;
+  }
+  else {
+    return 1;
+  }
+}
+
+/** Used for sampling curves along the profile's path. Any points more than the number of user-
+ * defined points will be evenly distributed among the curved edges. Then the remainders will be
+ * distributed to the most curved edges.
+ * \param n_segments: The number of segments to sample along the path. It must be higher than the
+ *        number of points used to define the profile (profile->path_len).
+ * \param sample_straight_edges: Whether to sample points between vector handle control points. If
+ *        this is true and there are only vector edges the straight edges will still be sampled.
+ * \param r_samples: An array of points to put the sampled positions. Must have length n_segments.
+ * \return r_samples: Fill the array with the sampled locations and if the point corresponds
+ *         to a control point, its handle type */
+void BKE_curveprofile_create_samples(CurveProfile *profile,
+                                     int n_segments,
+                                     bool sample_straight_edges,
+                                     CurveProfilePoint *r_samples)
+{
+  BezTriple *bezt;
+  int i, n_left, n_common, i_sample, n_curved_edges;
+  int *n_samples;
+  CurvatureSortPoint *curve_sorted;
+  int totpoints = profile->path_len;
+  int totedges = totpoints - 1;
+
+  BLI_assert(n_segments > 0);
+
+  /* Create Bezier points for calculating the higher resolution path. */
+  bezt = MEM_callocN(sizeof(BezTriple) * totpoints, "beztarr");
+  for (i = 0; i < totpoints; i++) {
+    bezt[i].vec[1][0] = profile->path[i].x;
+    bezt[i].vec[1][1] = profile->path[i].y;
+    bezt[i].h1 = (profile->path[i].h1 == HD_VECT) ? HD_VECT : HD_AUTO;
+    bezt[i].h2 = (profile->path[i].h2 == HD_VECT) ? HD_VECT : HD_AUTO;
+  }
+  /* Give the first and last bezier points the same handle type as their neighbors. */
+  if (totpoints > 2) {
+    bezt[0].h1 = bezt[0].h2 = bezt[1].h1;
+    bezt[totpoints - 1].h1 = bezt[totpoints - 1].h2 = bezt[totpoints - 2].h2;
+  }
+  /* Get handle positions for the bezier points. */
+  calchandle_profile(&bezt[0], NULL, &bezt[1]);
+  for (i = 1; i < totpoints - 1; i++) {
+    calchandle_profile(&bezt[i], &bezt[i - 1], &bezt[i + 1]);
+  }
+  calchandle_profile(&bezt[totpoints - 1], &bezt[totpoints - 2], NULL);
+
+  /* Create a list of edge indices with the most curved at the start, least curved at the end. */
+  curve_sorted = MEM_callocN(sizeof(CurvatureSortPoint) * totedges, "curve sorted");
+  for (i = 0; i < totedges; i++) {
+    curve_sorted[i].bezt_index = i;
+    /* Calculate the curvature of each edge once for use when sorting for curvature. */
+    curve_sorted[i].bezt_curvature = bezt_edge_handle_angle(bezt, i);
+  }
+  qsort(curve_sorted, (size_t)totedges, sizeof(CurvatureSortPoint), sort_points_curvature);
+
+  /* Assign the number of sampled points for each edge. */
+  n_samples = MEM_callocN(sizeof(int) * totedges, "create samples numbers");
+  int n_added = 0;
+  if (n_segments >= totedges) {
+    if (sample_straight_edges) {
+      /* Assign an even number to each edge if it’s possible, then add the remainder of sampled
+       * points starting with the most curved edges. */
+      n_common = n_segments / totedges;
+      n_left = n_segments % totedges;
+
+      /* Assign the points that fill fit evenly to the edges. */
+      if (n_common > 0) {
+        for (i = 0; i < totedges; i++) {
+          n_samples[i] = n_common;
+          n_added += n_common;
+        }
+      }
+    }
+    else {
+      /* Count the number of curved edges */
+      n_curved_edges = 0;
+      for (i = 0; i < totedges; i++) {
+        if (is_curved_edge(bezt, i)) {
+          n_curved_edges++;
+        }
+      }
+      /* Just sample all of the edges if there are no curved edges. */
+      n_curved_edges = (n_curved_edges == 0) ? totedges : n_curved_edges;
+
+      /* Give all of the curved edges the same number of points and straight edges one point. */
+      n_left = n_segments - (totedges - n_curved_edges); /* Left after 1 for each straight edge. */
+      n_common = n_left / n_curved_edges;                /* Number assigned to all curved edges */
+      if (n_common > 0) {
+        for (i = 0; i < totedges; i++) {
+          /* Add the common number if it's a curved edge or if edges are curved. */
+          if (is_curved_edge(bezt, i) || n_curved_edges == totedges) {
+            n_samples[i] += n_common;
+            n_added += n_common;
+          }
+          else {
+            n_samples[i] = 1;
+            n_added++;
+          }
+        }
+      }
+      n_left -= n_common * n_curved_edges;
+    }
+  }
+  else {
+    /* Not enough segments to give one to each edge, so just give them to the most curved edges. */
+    n_left = n_segments;
+  }
+  /* Assign the remainder of the points that couldn't be spread out evenly. */
+  BLI_assert(n_left < totedges);
+  for (i = 0; i < n_left; i++) {
+    n_samples[curve_sorted[i].bezt_index]++;
+    n_added++;
+  }
+
+  BLI_assert(n_added == n_segments); /* n_added is just used for this assert, could remove it. */
+
+  /* Sample the points and add them to the locations table. */
+  for (i_sample = 0, i = 0; i < totedges; i++) {
+    if (n_samples[i] > 0) {
+      /* Carry over the handle types from the control point to its first corresponding sample. */
+      r_samples[i_sample].h1 = profile->path[i].h1;
+      r_samples[i_sample].h2 = profile->path[i].h2;
+      /* All extra sample points for this control point get "auto" handles. */
+      for (int j = i_sample + 1; j < i_sample + n_samples[i]; j++) {
+        r_samples[j].flag = 0;
+        r_samples[j].h1 = HD_AUTO;
+        r_samples[j].h2 = HD_AUTO;
+        BLI_assert(j < n_segments);
+      }
+
+      /* Do the sampling from bezier points, X values first, then Y values. */
+      BKE_curve_forward_diff_bezier(bezt[i].vec[1][0],
+                                    bezt[i].vec[2][0],
+                                    bezt[i + 1].vec[0][0],
+                                    bezt[i + 1].vec[1][0],
+                                    &r_samples[i_sample].x,
+                                    n_samples[i],
+                                    sizeof(CurveProfilePoint));
+      BKE_curve_forward_diff_bezier(bezt[i].vec[1][1],
+                                    bezt[i].vec[2][1],
+                                    bezt[i + 1].vec[0][1],
+                                    bezt[i + 1].vec[1][1],
+                                    &r_samples[i_sample].y,
+                                    n_samples[i],
+                                    sizeof(CurveProfilePoint));
+    }
+    i_sample += n_samples[i]; /* Add the next set of points after the ones we just added. */
+    BLI_assert(i_sample <= n_segments);
+  }
+
+#ifdef DEBUG_profile_TABLE
+  printf("CURVEPROFILE CREATE SAMPLES\n");
+  printf("n_segments: %d\n", n_segments);
+  printf("totedges: %d\n", totedges);
+  printf("n_common: %d\n", n_common);
+  printf("n_left: %d\n", n_left);
+  printf("n_samples: ");
+  for (i = 0; i < totedges; i++) {
+    printf("%d, ", n_samples[i]);
+  }
+  printf("\n");
+  printf("i_curved_sorted: ");
+  for (i = 0; i < totedges; i++) {
+    printf("(%d %.2f), ", curve_sorted[i].bezt_index, curve_sorted[i].bezt_curvature);
+  }
+  printf("\n");
+#endif
+  MEM_freeN(bezt);
+  MEM_freeN(curve_sorted);
+  MEM_freeN(n_samples);
+}
+
+/** Creates a higher resolution table by sampling the curved points. This table is used for display
+ * and evenly spaced evaluation. */
+static void curveprofile_make_table(CurveProfile *profile)
+{
+  int n_samples = PROF_N_TABLE(profile->path_len);
+  CurveProfilePoint *new_table = MEM_callocN(sizeof(CurveProfilePoint) * (n_samples + 1),
+                                             "high-res table");
+
+  BKE_curveprofile_create_samples(profile, n_samples - 1, false, new_table);
+  /* Manually add last point at the end of the profile */
+  new_table[n_samples - 1].x = 0.0f;
+  new_table[n_samples - 1].y = 1.0f;
+
+  if (profile->table) {
+    MEM_freeN(profile->table);
+  }
+  profile->table = new_table;
+}
+
+/** Creates the table of points used for displaying a preview of the sampled segment locations on
+ * the widget itself. */
+static void CurveProfile_make_segments_table(CurveProfile *profile)
+{
+  int n_samples = profile->segments_len;
+  if (n_samples <= 0) {
+    return;
+  }
+  CurveProfilePoint *new_table = MEM_callocN(sizeof(CurveProfilePoint) * (n_samples + 1),
+                                             "samples table");
+
+  if (profile->flag & PROF_SAMPLE_EVEN_LENGTHS) {
+    /* Even length sampling incompatible with only straight edge sampling for now. */
+    BKE_curveprofile_create_samples_even_spacing(profile, n_samples, new_table);
+  }
+  else {
+    BKE_curveprofile_create_samples(
+        profile, n_samples, profile->flag & PROF_SAMPLE_STRAIGHT_EDGES, new_table);
+  }
+
+  if (profile->segments) {
+    MEM_freeN(profile->segments);
+  }
+  profile->segments = new_table;
+}
+
+/** Sets the default settings and clip range for the profile widget. Does not generate either
+ * table. */
+void BKE_curveprofile_set_defaults(CurveProfile *profile)
+{
+  profile->flag = PROF_USE_CLIP;
+
+  BLI_rctf_init(&profile->view_rect, 0.0f, 1.0f, 0.0f, 1.0f);
+  profile->clip_rect = profile->view_rect;
+
+  profile->path_len = 2;
+  profile->path = MEM_callocN(2 * sizeof(CurveProfilePoint), "path points");
+
+  profile->path[0].x = 1.0f;
+  profile->path[0].y = 0.0f;
+  profile->path[1].x = 1.0f;
+  profile->path[1].y = 1.0f;
+
+  profile->changed_timestamp = 0;
+}
+
+/** Returns a pointer to a newly allocated curve profile, using the given preset.
+  \param preset: Value in eCurveProfilePresets. */
+struct CurveProfile *BKE_curveprofile_add(int preset)
+{
+  CurveProfile *profile = MEM_callocN(sizeof(CurveProfile), "curve profile");
+
+  BKE_curveprofile_set_defaults(profile);
+  profile->preset = preset;
+  BKE_curveprofile_reset(profile);
+  curveprofile_make_table(profile);
+
+  return profile;
+}
+
+/** Should be called after the widget is changed. Does profile and remove double checks and more
+ * importantly, recreates the display / evaluation and segments tables. */
+void BKE_curveprofile_update(CurveProfile *profile, const bool remove_double)
+{
+  CurveProfilePoint *points = profile->path;
+  rctf *clipr = &profile->clip_rect;
+  float thresh;
+  float dx, dy;
+  int i;
+
+  profile->changed_timestamp++;
+
+  /* Clamp with the clipping rect in case something got past. */
+  if (profile->flag & PROF_USE_CLIP) {
+    /* Move points inside the clip rectangle. */
+    for (i = 0; i < profile->path_len; i++) {
+      points[i].x = max_ff(points[i].x, clipr->xmin);
+      points[i].x = min_ff(points[i].x, clipr->xmax);
+      points[i].y = max_ff(points[i].y, clipr->ymin);
+      points[i].y = min_ff(points[i].y, clipr->ymax);
+    }
+    /* Ensure zoom-level respects clipping. */
+    if (BLI_rctf_size_x(&profile->view_rect) > BLI_rctf_size_x(&profile->clip_rect)) {
+      profile->view_rect.xmin = profile->clip_rect.xmin;
+      profile->view_rect.xmax = profile->clip_rect.xmax;
+    }
+    if (BLI_rctf_size_y(&profile->view_rect) > BLI_rctf_size_y(&profile->clip_rect)) {
+      profile->view_rect.ymin = profile->clip_rect.ymin;
+      profile->view_rect.ymax = profile->clip_rect.ymax;
+    }
+  }
+
+  /* Remove doubles with a threshold set at 1% of default range. */
+  thresh = 0.01f * BLI_rctf_size_x(clipr);
+  if (remove_double && profile->path_len > 2) {
+    for (i = 0; i < profile->path_len - 1; i++) {
+      dx = points[i].x - points[i + 1].x;
+      dy = points[i].y - points[i + 1].y;
+      if (sqrtf(dx * dx + dy * dy) < thresh) {
+        if (i == 0) {
+          points[i + 1].flag |= HD_VECT;
+          if (points[i + 1].flag & PROF_SELECT) {
+            points[i].flag |= PROF_SELECT;
+          }
+        }
+        else {
+          points[i].flag |= HD_VECT;
+          if (points[i].flag & PROF_SELECT) {
+            points[i + 1].flag |= PROF_SELECT;
+          }
+        }
+        break; /* Assumes 1 deletion per edit is ok. */
+      }
+    }
+    if (i != profile->path_len - 1) {
+      BKE_curveprofile_remove_by_flag(profile, 2);
+    }
+  }
+
+  /* Create the high resolution table for drawing and some evaluation functions. */
+  curveprofile_make_table(profile);
+
+  /* Store a table of samples for the segment locations for a preview and the table's user. */
+  if (profile->segments_len > 0) {
+    CurveProfile_make_segments_table(profile);
+  }
+}
+
+/** Refreshes the higher resolution table sampled from the input points. A call to this or
+ * curveprofile_update is needed before evaluation functions that use the table. Also sets the
+ * number of segments used for the display preview of the locations of the sampled points. */
+void BKE_curveprofile_initialize(CurveProfile *profile, short segments_len)
+{
+  profile->segments_len = segments_len;
+
+  /* Calculate the higher resolution / segments tables for display and evaluation. */
+  BKE_curveprofile_update(profile, false);
+}
+
+/** Gives the distance to the next point in the widget's sampled table, in other words the length
+ * of the ith edge of the table.
+ * \note Requires curveprofile_initialize or curveprofile_update call before to fill table. */
+static float curveprofile_distance_to_next_table_point(const CurveProfile *profile, int i)
+{
+  BLI_assert(i < PROF_N_TABLE(profile->path_len));
+
+  return len_v2v2(&profile->table[i].x, &profile->table[i + 1].x);
+}
+
+/** Calculates the total length of the profile from the curves sampled in the table.
+ * \note Requires curveprofile_initialize or curveprofile_update call before to fill table. */
+float BKE_curveprofile_total_length(const CurveProfile *profile)
+{
+  float total_length = 0;
+  for (int i = 0; i < PROF_N_TABLE(profile->path_len) - 1; i++) {
+    total_length += len_v2v2(&profile->table[i].x, &profile->table[i + 1].x);
+  }
+  return total_length;
+}
+
+/** Samples evenly spaced positions along the curve profile's table (generated from path). Fills
+ * an entire table at once for a speedup if all of the results are going to be used anyway.
+ * \note Requires curveprofile_initialize or curveprofile_update call before to fill table.
+ * \note Working, but would conflict with "Sample Straight Edges" option, so this is unused for
+ * now. */
+void BKE_curveprofile_create_samples_even_spacing(CurveProfile *profile,
+                                                  int n_segments,
+                                                  CurveProfilePoint *r_samples)
+{
+  const float total_length = BKE_curveprofile_total_length(profile);
+  const float segment_length = total_length / n_segments;
+  float length_travelled = 0.0f;
+  float distance_to_next_table_point = curveprofile_distance_to_next_table_point(profile, 0);
+  float distance_to_previous_table_point = 0.0f;
+  float segment_left, factor;
+  int i_table = 0;
+
+  /* Set the location for the first point. */
+  r_samples[0].x = profile->table[0].x;
+  r_samples[0].y = profile->table[0].y;
+
+  /* Travel along the path, recording the locations of segments as we pass them. */
+  segment_left = segment_length;
+  for (int i = 1; i < n_segments; i++) {
+    /* Travel over all of the points that fit inside this segment. */
+    while (distance_to_next_table_point < segment_left) {
+      length_travelled += distance_to_next_table_point;
+      segment_left -= distance_to_next_table_point;
+      i_table++;
+      distance_to_next_table_point = curveprofile_distance_to_next_table_point(profile, i_table);
+      distance_to_previous_table_point = 0.0f;
+    }
+    /* We're at the last table point that fits inside the current segment, use interpolation. */
+    factor = (distance_to_previous_table_point + segment_left) /
+             (distance_to_previous_table_point + distance_to_next_table_point);
+    r_samples[i].x = interpf(profile->table[i_table + 1].x, profile->table[i_table].x, factor);
+    r_samples[i].y = interpf(profile->table[i_table + 1].y, profile->table[i_table].y, factor);
+#ifdef DEBUG_CURVEPROFILE_EVALUATE
+    BLI_assert(factor <= 1.0f && factor >= 0.0f);
+    printf("segment_left: %.3f\n", segment_left);
+    printf("i_table: %d\n", i_table);
+    printf("distance_to_previous_table_point: %.3f\n", distance_to_previous_table_point);
+    printf("distance_to_next_table_point: %.3f\n", distance_to_next_table_point);
+    printf("Interpolating with factor %.3f from (%.3f, %.3f) to (%.3f, %.3f)\n\n",
+           factor,
+           profile->table[i_table].x,
+           profile->table[i_table].y,
+           profile->table[i_table + 1].x,
+           profile->table[i_table + 1].y);
+#endif
+
+    /* We sampled in between this table point and the next, so the next travel step is smaller. */
+    distance_to_next_table_point -= segment_left;
+    distance_to_previous_table_point += segment_left;
+    length_travelled += segment_left;
+    segment_left = segment_length;
+  }
+}
+
+/** Does a single evaluation along the profile's path. Travels down (length_portion * path) length
+ * and returns the position at that point.
+ * \param length_portion: The portion (0 to 1) of the path's full length to sample at.
+ * \note Requires curveprofile_initialize or curveprofile_update call before to fill table */
+void BKE_curveprofile_evaluate_length_portion(const CurveProfile *profile,
+                                              float length_portion,
+                                              float *x_out,
+                                              float *y_out)
+{
+  const float total_length = BKE_curveprofile_total_length(profile);
+  const float requested_length = length_portion * total_length;
+
+  /* Find the last point along the path with a lower length portion than the input. */
+  int i = 0;
+  float length_travelled = 0.0f;
+  while (length_travelled < requested_length) {
+    /* Check if we reached the last point before the final one. */
+    if (i == PROF_N_TABLE(profile->path_len) - 2) {
+      break;
+    }
+    float new_length = curveprofile_distance_to_next_table_point(profile, i);
+    if (length_travelled + new_length >= requested_length) {
+      break;
+    }
+    length_travelled += new_length;
+    i++;
+  }
+
+  /* Now travel the remaining distance of length portion down the path to the next point and
+   * find the location where we stop. */
+  float distance_to_next_point = curveprofile_distance_to_next_table_point(profile, i);
+  float lerp_factor = (requested_length - length_travelled) / distance_to_next_point;
+
+#ifdef DEBUG_CURVEPROFILE_EVALUATE
+  printf("CURVEPROFILE EVALUATE\n");
+  printf("  length portion input: %f\n", (double)length_portion);
+  printf("  requested path length: %f\n", (double)requested_length);
+  printf("  distance to next point: %f\n", (double)distance_to_next_point);
+  printf("  length travelled: %f\n", (double)length_travelled);
+  printf("  lerp-factor: %f\n", (double)lerp_factor);
+  printf("  ith point (%f, %f)\n", (double)profile->path[i].x, (double)profile->path[i].y);
+  printf("  next point(%f, %f)\n", (double)profile->path[i + 1].x, (double)profile->path[i + 1].y);
+#endif
+
+  *x_out = interpf(profile->table[i].x, profile->table[i + 1].x, lerp_factor);
+  *y_out = interpf(profile->table[i].y, profile->table[i + 1].y, lerp_factor);
+}
index 53e5f1fdfe5050f0c48d8efe2703b2806b0034a7..2753cbd9be2691ea795dc0513a19361fa775938b 100644 (file)
@@ -29,6 +29,7 @@
 
 #include "DNA_anim_types.h"
 #include "DNA_collection_types.h"
+#include "DNA_curveprofile_types.h"
 #include "DNA_linestyle_types.h"
 #include "DNA_mesh_types.h"
 #include "DNA_node_types.h"
@@ -80,6 +81,7 @@
 #include "BKE_node.h"
 #include "BKE_object.h"
 #include "BKE_paint.h"
+#include "BKE_curveprofile.h"
 #include "BKE_rigidbody.h"
 #include "BKE_scene.h"
 #include "BKE_screen.h"
@@ -182,6 +184,8 @@ ToolSettings *BKE_toolsettings_copy(ToolSettings *toolsettings, const int flag)
   /* duplicate Grease Pencil multiframe fallof */
   ts->gp_sculpt.cur_falloff = BKE_curvemapping_copy(ts->gp_sculpt.cur_falloff);
   ts->gp_sculpt.cur_primitive = BKE_curvemapping_copy(ts->gp_sculpt.cur_primitive);
+
+  ts->custom_bevel_profile_preset = BKE_curveprofile_copy(ts->custom_bevel_profile_preset);
   return ts;
 }
 
@@ -224,6 +228,10 @@ void BKE_toolsettings_free(ToolSettings *toolsettings)
     BKE_curvemapping_free(toolsettings->gp_sculpt.cur_primitive);
   }
 
+  if (toolsettings->custom_bevel_profile_preset) {
+    BKE_curveprofile_free(toolsettings->custom_bevel_profile_preset);
+  }
+
   MEM_freeN(toolsettings);
 }
 
@@ -729,6 +737,9 @@ void BKE_scene_init(Scene *sce)
     copy_v3_v3(gp_brush->curcolor_sub, curcolor_sub);
   }
 
+  /* Curve Profile */
+  sce->toolsettings->custom_bevel_profile_preset = BKE_curveprofile_add(PROF_PRESET_LINE);
+
   for (int i = 0; i < ARRAY_SIZE(sce->orientation_slots); i++) {
     sce->orientation_slots[i].index_custom = -1;
   }
index 167917f7b6ac6bc164e37ac9541c8dddbfb949c9..f6c1cd0380a1b87f46acb3d613f656ae6b68d318 100644 (file)
@@ -74,6 +74,7 @@
 #include "DNA_object_types.h"
 #include "DNA_packedFile_types.h"
 #include "DNA_particle_types.h"
+#include "DNA_curveprofile_types.h"
 #include "DNA_lightprobe_types.h"
 #include "DNA_rigidbody_types.h"
 #include "DNA_text_types.h"
 #include "BKE_paint.h"
 #include "BKE_particle.h"
 #include "BKE_pointcache.h"
+#include "BKE_curveprofile.h"
 #include "BKE_report.h"
 #include "BKE_scene.h"
 #include "BKE_screen.h"
@@ -2697,6 +2699,19 @@ static void direct_link_curvemapping(FileData *fd, CurveMapping *cumap)
 
 /** \} */
 
+/* -------------------------------------------------------------------- */
+/** \name Read CurveProfile
+ * \{ */
+
+static void direct_link_curveprofile(FileData *fd, CurveProfile *profile)
+{
+  profile->path = newdataadr(fd, profile->path);
+  profile->table = NULL;
+  profile->segments = NULL;
+}
+
+/** \} */
+
 /* -------------------------------------------------------------------- */
 /** \name Read ID: Brush
  * \{ */
@@ -5802,6 +5817,13 @@ static void direct_link_modifiers(FileData *fd, ListBase *lb)
         }
       }
     }
+    else if (md->type == eModifierType_Bevel) {
+      BevelModifierData *bmd = (BevelModifierData *)md;
+      bmd->custom_profile = newdataadr(fd, bmd->custom_profile);
+      if (bmd->custom_profile) {
+        direct_link_curveprofile(fd, bmd->custom_profile);
+      }
+    }
   }
 }
 
@@ -6758,6 +6780,13 @@ static void direct_link_scene(FileData *fd, Scene *sce)
     if (sce->toolsettings->gp_sculpt.cur_primitive) {
       direct_link_curvemapping(fd, sce->toolsettings->gp_sculpt.cur_primitive);
     }
+
+    /* Relink toolsettings curve profile */
+    sce->toolsettings->custom_bevel_profile_preset = newdataadr(
+        fd, sce->toolsettings->custom_bevel_profile_preset);
+    if (sce->toolsettings->custom_bevel_profile_preset) {
+      direct_link_curveprofile(fd, sce->toolsettings->custom_bevel_profile_preset);
+    }
   }
 
   if (sce->ed) {
index 50363e3f42a7b7280c44259cbe638abe360e8430..761424a587950b999924916d7905883a1259746a 100644 (file)
@@ -36,6 +36,7 @@
 #include "DNA_cloth_types.h"
 #include "DNA_collection_types.h"
 #include "DNA_constraint_types.h"
+#include "DNA_curveprofile_types.h"
 #include "DNA_gpu_types.h"
 #include "DNA_light_types.h"
 #include "DNA_layer_types.h"
@@ -74,6 +75,7 @@
 #include "BKE_node.h"
 #include "BKE_paint.h"
 #include "BKE_pointcache.h"
+#include "BKE_curveprofile.h"
 #include "BKE_report.h"
 #include "BKE_rigidbody.h"
 #include "BKE_screen.h"
@@ -3932,10 +3934,35 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain)
 
   {
     /* Versioning code until next subversion bump goes here. */
+
     for (bScreen *screen = bmain->screens.first; screen; screen = screen->id.next) {
       for (ScrArea *sa = screen->areabase.first; sa; sa = sa->next) {
         sa->flag &= ~AREA_FLAG_UNUSED_6;
       }
     }
+
+    /* Add custom curve profile to toolsettings for bevel tool */
+    if (!DNA_struct_elem_find(fd->filesdna, "ToolSettings", "CurveProfile", "custom_profile")) {
+      for (Scene *scene = bmain->scenes.first; scene; scene = scene->id.next) {
+        ToolSettings *ts = scene->toolsettings;
+        if ((ts) && (ts->custom_bevel_profile_preset == NULL)) {
+          ts->custom_bevel_profile_preset = BKE_curveprofile_add(PROF_PRESET_LINE);
+        }
+      }
+    }
+
+    /* Add custom curve profile to bevel modifier */
+    if (!DNA_struct_elem_find(fd->filesdna, "BevelModifier", "CurveProfile", "custom_profile")) {
+      for (Object *object = bmain->objects.first; object != NULL; object = object->id.next) {
+        for (ModifierData *md = object->modifiers.first; md; md = md->next) {
+          if (md->type == eModifierType_Bevel) {
+            BevelModifierData *bmd = (BevelModifierData *)md;
+            if (!bmd->custom_profile) {
+              bmd->custom_profile = BKE_curveprofile_add(PROF_PRESET_LINE);
+            }
+          }
+        }
+      }
+    }
   }
 }
index 45fb62a4ac0d3950d00e5405d777d7d53b91f9d1..05758b446ad1ee6f42c21cbe830f105a6ed45978 100644 (file)
@@ -27,6 +27,7 @@
 #include "BLI_system.h"
 
 #include "DNA_camera_types.h"
+#include "DNA_curveprofile_types.h"
 #include "DNA_gpencil_types.h"
 #include "DNA_mesh_types.h"
 #include "DNA_object_types.h"
@@ -51,6 +52,7 @@
 #include "BKE_paint.h"
 #include "BKE_screen.h"
 #include "BKE_workspace.h"
+#include "BKE_curveprofile.h"
 
 #include "BLO_readfile.h"
 
@@ -324,6 +326,11 @@ static void blo_update_defaults_scene(Main *bmain, Scene *scene)
       copy_v2_v2(me->mloopuv[i].uv, uv_values[i]);
     }
   }
+
+  /* Make sure that the curve profile is initialized */
+  if (ts->custom_bevel_profile_preset == NULL) {
+    ts->custom_bevel_profile_preset = BKE_curveprofile_add(PROF_PRESET_LINE);
+  }
 }
 
 /**
index 2966a030f6391403726fe28524db57eca0f16ac6..3390d30ad5d0ca6d7b5760bdbbe41715576f159d 100644 (file)
 #include "DNA_workspace_types.h"
 #include "DNA_movieclip_types.h"
 #include "DNA_mask_types.h"
+#include "DNA_curveprofile_types.h"
 
 #include "MEM_guardedalloc.h"  // MEM_freeN
 #include "BLI_bitmap.h"
@@ -953,6 +954,12 @@ static void write_curvemapping(WriteData *wd, CurveMapping *cumap)
   write_curvemapping_curves(wd, cumap);
 }
 
+static void write_CurveProfile(WriteData *wd, CurveProfile *profile)
+{
+  writestruct(wd, DATA, CurveProfile, 1, profile);
+  writestruct(wd, DATA, CurveProfilePoint, profile->path_len, profile->path);
+}
+
 static void write_node_socket(WriteData *wd, bNodeSocket *sock)
 {
   /* actual socket writing */
@@ -1759,6 +1766,12 @@ static void write_modifiers(WriteData *wd, ListBase *modbase)
         }
       }
     }
+    else if (md->type == eModifierType_Bevel) {
+      BevelModifierData *bmd = (BevelModifierData *)md;
+      if (bmd->custom_profile) {
+        write_CurveProfile(wd, bmd->custom_profile);
+      }
+    }
   }
 }
 
@@ -2533,6 +2546,10 @@ static void write_scene(WriteData *wd, Scene *sce)
   if (tos->gp_sculpt.cur_primitive) {
     write_curvemapping(wd, tos->gp_sculpt.cur_primitive);
   }
+  /* Write the curve profile to the file. */
+  if (tos->custom_bevel_profile_preset) {
+    write_CurveProfile(wd, tos->custom_bevel_profile_preset);
+  }
 
   write_paint(wd, &tos->imapaint.paint);
 
index 7086cea1aceeec513d9c0eee331e2ff4de396f06..74d01dca66ab5ce1cad9e974ce570a1adf2637d1 100644 (file)
@@ -1725,6 +1725,12 @@ static BMO_FlagSet bmo_enum_bevel_miter_type[] = {
   {0, NULL},
 };
 
+static BMO_FlagSet bmo_enum_bevel_vmesh_method[] = {
+  {BEVEL_VMESH_ADJ, "ADJ"},
+  {BEVEL_VMESH_CUTOFF, "CUTOFF"},
+  {0, NULL},
+};
+
 /*
  * Bevel.
  *
@@ -1735,7 +1741,8 @@ static BMOpDefine bmo_bevel_def = {
   /* slots_in */
   {{"geom", BMO_OP_SLOT_ELEMENT_BUF, {BM_VERT | BM_EDGE | BM_FACE}},     /* input edges and vertices */
    {"offset", BMO_OP_SLOT_FLT},           /* amount to offset beveled edge */
-   {"offset_type", BMO_OP_SLOT_INT, {(int)BMO_OP_SLOT_SUBTYPE_INT_ENUM}, bmo_enum_bevel_offset_type}, /* how to measure the offset */
+   {"offset_type", BMO_OP_SLOT_INT, {(int)BMO_OP_SLOT_SUBTYPE_INT_ENUM},
+    bmo_enum_bevel_offset_type}, /* how to measure the offset */
    {"segments", BMO_OP_SLOT_INT},         /* number of segments in bevel */
    {"profile", BMO_OP_SLOT_FLT},          /* profile shape, 0->1 (.5=>round) */
    {"vertex_only", BMO_OP_SLOT_BOOL},     /* only bevel vertices, not edges */
@@ -1746,13 +1753,18 @@ static BMOpDefine bmo_bevel_def = {
    {"mark_sharp", BMO_OP_SLOT_BOOL},      /* extend edge data to allow sharp edges to run across bevels */
    {"harden_normals", BMO_OP_SLOT_BOOL},  /* harden normals */
    {"face_strength_mode", BMO_OP_SLOT_INT, {(int)BMO_OP_SLOT_SUBTYPE_INT_ENUM},
-    bmo_enum_bevel_face_strength_type}, /* whether to set face strength, and which faces to set if so */
+    bmo_enum_bevel_face_strength_type},   /* whether to set face strength, and which faces to set if so */
    {"miter_outer", BMO_OP_SLOT_INT, {(int)BMO_OP_SLOT_SUBTYPE_INT_ENUM},
-    bmo_enum_bevel_miter_type},         /* outer miter kind */
+    bmo_enum_bevel_miter_type},           /* outer miter kind */
    {"miter_inner", BMO_OP_SLOT_INT, {(int)BMO_OP_SLOT_SUBTYPE_INT_ENUM},
-    bmo_enum_bevel_miter_type},         /* outer miter kind */
+    bmo_enum_bevel_miter_type},           /* outer miter kind */
    {"spread", BMO_OP_SLOT_FLT},           /* amount to offset beveled edge */
    {"smoothresh", BMO_OP_SLOT_FLT},       /* for passing mesh's smoothresh, used in hardening */
+   {"use_custom_profile", BMO_OP_SLOT_BOOL}, /* Whether to use custom profile feature */
+   /* the ProfileWiget struct for the custom profile shape */
+   {"custom_profile", BMO_OP_SLOT_PTR, {(int)BMO_OP_SLOT_SUBTYPE_PTR_STRUCT}},
+   {"vmesh_method", BMO_OP_SLOT_INT, {(int)BMO_OP_SLOT_SUBTYPE_INT_ENUM},
+    bmo_enum_bevel_vmesh_method},
    {{'\0'}},
   },
   /* slots_out */
index 2039289dcd7f61f17ad31ca5009a481afdda1e9d..dbd2bf076c648bf997d5d67040ca36ad8ff21c9c 100644 (file)
@@ -237,6 +237,7 @@ typedef enum eBMOpSlotSubType_Ptr {
   BMO_OP_SLOT_SUBTYPE_PTR_SCENE = 101,
   BMO_OP_SLOT_SUBTYPE_PTR_OBJECT = 102,
   BMO_OP_SLOT_SUBTYPE_PTR_MESH = 103,
+  BMO_OP_SLOT_SUBTYPE_PTR_STRUCT = 104,
 } eBMOpSlotSubType_Ptr;
 typedef enum eBMOpSlotSubType_Int {
   BMO_OP_SLOT_SUBTYPE_INT_ENUM = 200,
@@ -294,8 +295,8 @@ typedef struct BMOpSlot {
   BLI_assert(((slot >= (op)->slots_in) && (slot < &(op)->slots_in[BMO_OP_MAX_SLOTS])) || \
              ((slot >= (op)->slots_out) && (slot < &(op)->slots_out[BMO_OP_MAX_SLOTS])))
 
-/* way more than probably needed, compiler complains if limit hit */
-#define BMO_OP_MAX_SLOTS 20
+/* Limit hit, so expanded for bevel operator. Compiler complains if limit is hit. */
+#define BMO_OP_MAX_SLOTS 21
 
 /* BMOpDefine->type_flag */
 typedef enum {
index 78e8ce04115c75163aba632e8b7f5b02fe7c4811..9f0107db6936db754f8fcbcf1cdde78aae35719e 100644 (file)
@@ -126,6 +126,12 @@ enum {
   BEVEL_MITER_ARC,
 };
 
+/* Bevel vertex mesh creation methods */
+enum {
+  BEVEL_VMESH_ADJ,
+  BEVEL_VMESH_CUTOFF,
+};
+
 /* Normal Face Strength values */
 enum {
   FACE_STRENGTH_WEAK = -16384,
index 33be9559db351b565f828f8f1008b4b214af31e9..1d5bcf8b160187ccd0e790e6e8403aa7ffda3b48 100644 (file)
@@ -24,6 +24,8 @@
 
 #include "bmesh.h"
 #include "bmesh_tools.h"
+#include "BKE_curveprofile.h"
+#include "DNA_curveprofile_types.h"
 
 #include "intern/bmesh_operators_private.h" /* own include */
 
@@ -45,6 +47,9 @@ void bmo_bevel_exec(BMesh *bm, BMOperator *op)
   const int miter_inner = BMO_slot_int_get(op->slots_in, "miter_inner");
   const float spread = BMO_slot_float_get(op->slots_in, "spread");
   const float smoothresh = BMO_slot_float_get(op->slots_in, "smoothresh");
+  const bool use_custom_profile = BMO_slot_bool_get(op->slots_in, "use_custom_profile");
+  const CurveProfile *custom_profile = BMO_slot_ptr_get(op->slots_in, "custom_profile");
+  const int vmesh_method = BMO_slot_int_get(op->slots_in, "vmesh_method");
 
   if (offset > 0) {
     BMOIter siter;
@@ -87,7 +92,10 @@ void bmo_bevel_exec(BMesh *bm, BMOperator *op)
                   miter_outer,
                   miter_inner,
                   spread,
-                  smoothresh);
+                  smoothresh,
+                  use_custom_profile,
+                  custom_profile,
+                  vmesh_method);
 
     BMO_slot_buffer_from_enabled_hflag(bm, op, op->slots_out, "faces.out", BM_FACE, BM_ELEM_TAG);
     BMO_slot_buffer_from_enabled_hflag(bm, op, op->slots_out, "edges.out", BM_EDGE, BM_ELEM_TAG);
index 797e2ca864e674d30732f6f1e8ff98ecac74b2ed..6989946147eca6021c948773dc7bc864b779aa2f 100644 (file)
@@ -38,6 +38,9 @@
 
 #include "eigen_capi.h"
 
+#include "BKE_curveprofile.h"
+#include "DNA_curveprofile_types.h"
+
 #include "bmesh.h"
 #include "bmesh_bevel.h" /* own include */
 
 #define BEVEL_EPSILON_BIG_SQ 1e-8f
 #define BEVEL_EPSILON_ANG DEG2RADF(2.0f)
 #define BEVEL_SMALL_ANG DEG2RADF(10.0f)
+/** Difference in dot products that corresponds to 10 degree difference between vectors. */
+#define BEVEL_SMALL_ANG_DOT 1 - cosf(BEVEL_SMALL_ANG)
 #define BEVEL_MAX_ADJUST_PCT 10.0f
 #define BEVEL_MAX_AUTO_ADJUST_PCT 300.0f
 #define BEVEL_MATCH_SPEC_WEIGHT 0.2
 
+//#define DEBUG_CUSTOM_PROFILE_CUTOFF
+//#define DEBUG_CUSTOM_PROFILE_SAMPLE
+
+#if defined(DEBUG_PROFILE_ORIENTATION_DRAW) || defined(DEBUG_CUSTOM_PROFILE_PIPE)
+static float debug_color_red[4] = {1.0f, 0.0f, 0.0f, 1.0f};
+static float debug_color_blue[4] = {0.0f, 0.0f, 1.0f, 1.0f};
+extern void DRW_debug_sphere(const float center[3], const float radius, const float color[4]);
+extern void DRW_debug_line_v3v3(const float v1[3], const float v2[3], const float color[4]);
+#endif
+
 /* happens far too often, uncomment for development */
 // #define BEVEL_ASSERT_PROJECT
 
 typedef struct NewVert {
   BMVert *v;
   float co[3];
-  //  int _pad;
+  char _pad[4];
 } NewVert;
 
 struct BoundVert;
 
 /* Data for one end of an edge involved in a bevel */
 typedef struct EdgeHalf {
-  struct EdgeHalf *next, *prev; /* in CCW order */
-  BMEdge *e;                    /* original mesh edge */
-  BMFace *fprev;                /* face between this edge and previous, if any */
-  BMFace *fnext;                /* face between this edge and next, if any */
-  struct BoundVert *leftv;      /* left boundary vert (looking along edge to end) */
-  struct BoundVert *rightv;     /* right boundary vert, if beveled */
-  int profile_index;            /* offset into profile to attach non-beveled edge */
-  int seg;                      /* how many segments for the bevel */
-  float offset_l;               /* offset for this edge, on left side */
-  float offset_r;               /* offset for this edge, on right side */
-  float offset_l_spec;          /* user specification for offset_l */
-  float offset_r_spec;          /* user specification for offset_r */
-  bool is_bev;                  /* is this edge beveled? */
-  bool is_rev;                  /* is e->v2 the vertex at this end? */
-  bool is_seam;                 /* is e a seam for custom loopdata (e.g., UVs)? */
-  //  int _pad;
+  /** Other EdgeHalves connected to the same BevVert, in CCW order. */
+  struct EdgeHalf *next, *prev;
+  /** Original mesh edge */
+  BMEdge *e;
+  /** Face between this edge and previous, if any */
+  BMFace *fprev;
+  /** Face between this edge and next, if any */
+  BMFace *fnext;
+  /** Left boundary vert (looking along edge to end) */
+  struct BoundVert *leftv;
+  /** Right boundary vert, if beveled */
+  struct BoundVert *rightv;
+  /** Offset into profile to attach non-beveled edge */
+  int profile_index;
+  /** How many segments for the bevel */
+  int seg;
+  /** Offset for this edge, on left side */
+  float offset_l;
+  /** Offset for this edge, on right side */
+  float offset_r;
+  /** User specification for offset_l */
+  float offset_l_spec;
+  /** User specification for offset_r */
+  float offset_r_spec;
+  /** Is this edge beveled? */
+  bool is_bev;
+  /** Is e->v2 the vertex at this end? */
+  bool is_rev;
+  /** Is e a seam for custom loopdata (e.g., UVs)? */
+  bool is_seam;
+  /** Used during the custom profile orientation pass */
+  bool visited_rpo;
+  char _pad[4];
 } EdgeHalf;
 
-/* Profile specification.
+/* Profile specification:
  * Many interesting profiles are in family of superellipses:
  *     (abs(x/a))^r + abs(y/b))^r = 1
  * r==2 => ellipse; r==1 => line; r < 1 => concave; r > 1 => bulging out.
  * Special cases: let r==0 mean straight-inward, and r==4 mean straight outward.
- * The profile is an arc with control points coa, midco,
+ * The profile is a path defined with start, middle, and end control points
  * projected onto a plane (plane_no is normal, plane_co is a point on it)
  * via lines in a given direction (proj_dir).
  * After the parameters are all set, the actual profile points are calculated
- * and point in prof_co. We also may need profile points for a higher resolution
- * number of segments, in order to make the vertex mesh pattern, and that goes
- * in prof_co_2.
+ * and pointed to by prof_co. We also may need profile points for a higher resolution
+ * number of segments for the subdivision while making the ADJ vertex mesh pattern,
+ * and that goes in prof_co_2.
  */
 typedef struct Profile {
-  float super_r;     /* superellipse r parameter */
-  float coa[3];      /* start control point for profile */
-  float midco[3];    /* mid control point for profile */
-  float cob[3];      /* end control point for profile */
-  float plane_no[3]; /* normal of plane to project to */
-  float plane_co[3]; /* coordinate on plane to project to */
-  float proj_dir[3]; /* direction of projection line */
-  float *prof_co;    /* seg+1 profile coordinates (triples of floats) */
-  float *prof_co_2;  /* like prof_co, but for seg power of 2 >= seg */
+  /** Superellipse r parameter */
+  float super_r;
+  /** Height for profile cutoff face sides */
+  float height;
+  /** Start control point for profile */
+  float start[3];
+  /** Mid control point for profile */
+  float middle[3];
+  /** End control point for profile */
+  float end[3];
+  /** Normal of plane to project to */
+  float plane_no[3];
+  /** Coordinate on plane to project to */
+  float plane_co[3];
+  /** Direction of projection line */
+  float proj_dir[3];
+  /** seg+1 profile coordinates (triples of floats) */
+  float *prof_co;
+  /** Like prof_co, but for seg power of 2 >= seg */
+  float *prof_co_2;
 } Profile;
 #define PRO_SQUARE_R 1e4f
 #define PRO_CIRCLE_R 2.0f
 #define PRO_LINE_R 1.0f
 #define PRO_SQUARE_IN_R 0.0f
 
-/* Cache result of expensive calculation of u parameter values to
+/** The un-transformed 2D storage of profile vertex locations. Also for non-custom profiles
+ * this serves as a cache for the results of the expensive calculation of u parameter values to
  * get even spacing on superellipse for current BevelParams seg
  * and pro_super_r. */
 typedef struct ProfileSpacing {
-  double *xvals;   /* seg+1 x values */
-  double *xvals_2; /* seg_2+1 x values, seg_2 = power of 2 >= seg */
-  double *yvals;   /* seg+1 y values */
-  double *yvals_2; /* seg_2+1 y values, seg_2 = power of 2 >= seg */
-  int seg_2;       /* the seg_2 value */
+  /** The profile's seg+1 x values */
+  double *xvals;
+  /** The profile's seg+1 y values */
+  double *yvals;
+  /** The profile's seg_2+1 x values, (seg_2 = power of 2 >= seg) */
+  double *xvals_2;
+  /** The profile's seg_2+1 y values, (seg_2 = power of 2 >= seg) */
+  double *yvals_2;
+  /** The power of two greater than or equal to the number of segments. */
+  int seg_2;
+  /** How far "out" the profile is, used at the start of subdivision */
+  float fullness;
 } ProfileSpacing;
 
-/* An element in a cyclic boundary of a Vertex Mesh (VMesh) */
+/** An element in a cyclic boundary of a Vertex Mesh (VMesh) */
 typedef struct BoundVert {
   /** In CCW order. */
   struct BoundVert *next, *prev;
@@ -137,7 +188,7 @@ typedef struct BoundVert {
   /** First of edges attached here: in CCW order. */
   EdgeHalf *efirst;
   EdgeHalf *elast;
-  /** The "edge between" that this is on, in offset_on_edge_between case. */
+  /** The "edge between" that this boundvert on, in offset_on_edge_between case. */
   EdgeHalf *eon;
   /** Beveled edge whose left side is attached here, if any. */
   EdgeHalf *ebev;
@@ -157,26 +208,35 @@ typedef struct BoundVert {
   bool is_arc_start;
   /** This boundvert begins a patch profile */
   bool is_patch_start;
+  /** Is this boundvert the side of the custom profile's start */
+  bool is_profile_start;
+  char _pad[3];
   /** Length of seam starting from current boundvert to next boundvert with ccw ordering */
   int seam_len;
   /** Same as seam_len but defines length of sharp edges */
   int sharp_len;
-  //  int _pad;
 } BoundVert;
 
-/* Mesh structure replacing a vertex */
+/** Mesh structure replacing a vertex */
 typedef struct VMesh {
-  NewVert *mesh;         /* allocated array - size and structure depends on kind */
-  BoundVert *boundstart; /* start of boundary double-linked list */
-  int count;             /* number of vertices in the boundary */
-  int seg;               /* common # of segments for segmented edges */
+  /** Allocated array - size and structure depends on kind */
+  NewVert *mesh;
+  /** Start of boundary double-linked list */
+  BoundVert *boundstart;
+  /** Number of vertices in the boundary */
+  int count;
+  /** Common number of segments for segmented edges (same as bp->seg) */
+  int seg;
+  /** The kind of mesh to build at the corner vertex meshes */
   enum {
     M_NONE,    /* no polygon mesh needed */
     M_POLY,    /* a simple polygon */
     M_ADJ,     /* "adjacent edges" mesh pattern */
     M_TRI_FAN, /* a simple polygon - fan filled */
+    M_CUTOFF,  /* A triangulated face at the end of each profile */
   } mesh_kind;
-  //  int _pad;
+
+  int _pad;
 } VMesh;
 
 /* Data for a vertex involved in a bevel */
@@ -196,6 +256,7 @@ typedef struct BevVert {
   /** Used in graph traversal */
   bool visited;
   /** Array of size edgecount; CCW order from vertex normal side */
+  char _pad[6];
   EdgeHalf *edges;
   /** Array of size wirecount of wire edges */
   BMEdge **wire_edges;
@@ -209,7 +270,7 @@ typedef enum {
   F_NONE,
   /** Original face, not touched */
   F_ORIG,
-  /** Face for construction aroun a vert */
+  /** Face for construction around a vert */
   F_VERT,
   /** Face for a beveled edge */
   F_EDGE,
@@ -217,11 +278,21 @@ typedef enum {
   F_RECON,
 } FKind;
 
+/** Helper for keeping track of angle kind. */
+enum {
+  /** Angle less than 180 degrees */
+  ANGLE_SMALLER = -1,
+  /** 180 degree angle */
+  ANGLE_STRAIGHT = 0,
+  /** Angle greater than 180 degrees */
+  ANGLE_LARGER = 1,
+};
+
 #if 0
 static const char* fkind_names[] = {"F_NONE", "F_ORIG", "F_VERT", "F_EDGE", "F_RECON"}; /* DEBUG */
 #endif
 
-/* Bevel parameters and state */
+/** Bevel parameters and state */
 typedef struct BevelParams {
   /** Records BevVerts made: key BMVert*, value BevVert* */
   GHash *vert_hash;
@@ -229,9 +300,10 @@ typedef struct BevelParams {
   GHash *face_hash;
   /** Use for all allocs while bevel runs, if we need to free we can switch to mempool. */
   MemArena *mem_arena;
-  /** Parameter values for evenly spaced profiles. */
+  /** Profile vertex location and spacings */
   ProfileSpacing pro_spacing;
-
+  /** Parameter values for evenly spaced profile points for the miter profiles */
+  ProfileSpacing pro_spacing_miter;
   /** Blender units to offset each side of a beveled edge. */
   float offset;
   /** How offset is measured; enum defined in bmesh_operators.h */
@@ -258,6 +330,11 @@ typedef struct BevelParams {
   bool mark_sharp;
   /** Should we harden normals? */
   bool harden_normals;
+  /** Should we use the custom profiles feature? */
+  bool use_custom_profile;
+  char _pad[3];
+  /** The struct used to store the custom profile input */
+  const struct CurveProfile *custom_profile;
   /** Vertex group array, maybe set if vertex_only. */
   const struct MDeformVert *dvert;
   /** Vertex group index, maybe set if vertex_only. */
@@ -270,6 +347,8 @@ typedef struct BevelParams {
   int miter_outer;
   /** What kind of miter pattern to use on non-reflex angles. */
   int miter_inner;
+  /** The method to use for vertex mesh creation */
+  int vmesh_method;
   /** Amount to spread when doing inside miter. */
   float spread;
   /** Mesh's smoothresh, used if hardening. */
@@ -278,8 +357,6 @@ typedef struct BevelParams {
 
 // #pragma GCC diagnostic ignored "-Wpadded"
 
-// #include "bevdebug.c"
-
 /* Some flags to re-enable old behavior for a while,
  * in case fixes broke things not caught by regression tests. */
 static int bev_debug_flags = 0;
@@ -367,6 +444,7 @@ static BoundVert *add_new_bound_vert(MemArena *mem_arena, VMesh *vm, const float
   ans->any_seam = false;
   ans->is_arc_start = false;
   ans->is_patch_start = false;
+  ans->is_profile_start = false;
   vm->count++;
   return ans;
 }
@@ -628,7 +706,7 @@ static BMFace *bev_create_ngon(BMesh *bm,
   }
 
   if (mat_nr >= 0) {
-    f->mat_nr = mat_nr;
+    f->mat_nr = (short)mat_nr;
   }
   return f;
 }
@@ -812,7 +890,7 @@ static void bev_merge_edge_uvs(BMesh *bm, BMEdge *bme, BMVert *v)
 }
 
 /* Calculate coordinates of a point a distance d from v on e->e and return it in slideco */
-static void slide_dist(EdgeHalf *e, BMVert *v, float d, float slideco[3])
+static void slide_dist(EdgeHalf *e, BMVert *v, float d, float r_slideco[3])
 {
   float dir[3], len;
 
@@ -821,8 +899,8 @@ static void slide_dist(EdgeHalf *e, BMVert *v, float d, float slideco[3])
   if (d > len) {
     d = len - (float)(50.0 * BEVEL_EPSILON_D);
   }
-  copy_v3_v3(slideco, v->co);
-  madd_v3_v3fl(slideco, dir, -d);
+  copy_v3_v3(r_slideco, v->co);
+  madd_v3_v3fl(r_slideco, dir, -d);
 }
 
 /* Is co not on the edge e? if not, return the closer end of e in ret_closer_v */
@@ -873,13 +951,13 @@ static int edges_angle_kind(EdgeHalf *e1, EdgeHalf *e2, BMVert *v)
   }
   dot = dot_v3v3(cross, no);
   if (fabsf(dot) < BEVEL_EPSILON_BIG) {
-    return 0;
+    return ANGLE_STRAIGHT;
   }
   else if (dot < 0.0f) {
-    return 1;
+    return ANGLE_LARGER;
   }
   else {
-    return -1;
+    return ANGLE_SMALLER;
   }
 }
 
@@ -917,7 +995,7 @@ static bool point_between_edges(float co[3], BMVert *v, BMFace *f, EdgeHalf *e1,
 /*
  * Calculate the meeting point between the offset edges for e1 and e2, putting answer in meetco.
  * e1 and e2 share vertex v and face f (may be NULL) and viewed from the normal side of
- * the bevel vertex,  e1 precedes e2 in CCW order.
+ * the bevel vertex, e1 precedes e2 in CCW order.
  * Except: if edges_between is true, there are edges between e1 and e2 in CCW order so they
  * don't share a common face. We want the meeting point to be on an existing face so it
  * should be dropped onto one of the intermediate faces, if possible.
@@ -978,7 +1056,7 @@ static void offset_meet(
     normalize_v3(norm_perp1);
     copy_v3_v3(off1a, v->co);
     d = max_ff(e1->offset_r, e2->offset_l);
-    d = d / cos(ang / 2.0f);
+    d = d / cosf(ang / 2.0f);
     madd_v3_v3fl(off1a, norm_perp1, d);
     copy_v3_v3(meetco, off1a);
   }
@@ -1260,37 +1338,37 @@ static void set_profile_params(BevelParams *bp, BevVert *bv, BoundVert *bndv)
       negate_v3(pro->proj_dir);
     }
     normalize_v3(pro->proj_dir);
-    project_to_edge(e->e, co1, co2, pro->midco);
+    project_to_edge(e->e, co1, co2, pro->middle);
     if (DEBUG_OLD_PROJ_TO_PERP_PLANE) {
-      /* put arc endpoints on plane with normal proj_dir, containing midco */
+      /* put arc endpoints on plane with normal proj_dir, containing middle */
       add_v3_v3v3(co3, co1, pro->proj_dir);
-      if (!isect_line_plane_v3(pro->coa, co1, co3, pro->midco, pro->proj_dir)) {
+      if (!isect_line_plane_v3(pro->start, co1, co3, pro->middle, pro->proj_dir)) {
         /* shouldn't happen */
-        copy_v3_v3(pro->coa, co1);
+        copy_v3_v3(pro->start, co1);
       }
       add_v3_v3v3(co3, co2, pro->proj_dir);
-      if (!isect_line_plane_v3(pro->cob, co2, co3, pro->midco, pro->proj_dir)) {
+      if (!isect_line_plane_v3(pro->end, co2, co3, pro->middle, pro->proj_dir)) {
         /* shouldn't happen */
-        copy_v3_v3(pro->cob, co2);
+        copy_v3_v3(pro->end, co2);
       }
     }
     else {
-      copy_v3_v3(pro->coa, co1);
-      copy_v3_v3(pro->cob, co2);
+      copy_v3_v3(pro->start, co1);
+      copy_v3_v3(pro->end, co2);
     }
-    /* default plane to project onto is the one with triangle co1 - midco - co2 in it */
-    sub_v3_v3v3(d1, pro->midco, co1);
-    sub_v3_v3v3(d2, pro->midco, co2);
+    /* default plane to project onto is the one with triangle co1 - middle - co2 in it */
+    sub_v3_v3v3(d1, pro->middle, co1);
+    sub_v3_v3v3(d2, pro->middle, co2);
     normalize_v3(d1);
     normalize_v3(d2);
     cross_v3_v3v3(pro->plane_no, d1, d2);
     normalize_v3(pro->plane_no);
     if (nearly_parallel(d1, d2)) {
-      /* co1 - midco -co2 are collinear.
+      /* co1 - middle -co2 are collinear.
        * Should be case that beveled edge is coplanar with two boundary verts.
        * We want to move the profile to that common plane, if possible.
        * That makes the multi-segment bevels curve nicely in that plane, as users expect.
-       * The new midco should be either v (when neighbor edges are unbeveled)
+       * The new middle should be either v (when neighbor edges are unbeveled)
        * or the intersection of the offset lines (if they are).
        * If the profile is going to lead into unbeveled edges on each side
        * (that is, both BoundVerts are "on-edge" points on non-beveled edges)
@@ -1300,14 +1378,14 @@ static void set_profile_params(BevelParams *bp, BevVert *bv, BoundVert *bndv)
       }
       else {
         if (DEBUG_OLD_PROJ_TO_PERP_PLANE) {
-          copy_v3_v3(pro->coa, co1);
-          copy_v3_v3(pro->cob, co2);
+          copy_v3_v3(pro->start, co1);
+          copy_v3_v3(pro->end, co2);
         }
         if (DEBUG_OLD_FLAT_MID) {
-          copy_v3_v3(pro->midco, bv->v->co);
+          copy_v3_v3(pro->middle, bv->v->co);
         }
         else {
-          copy_v3_v3(pro->midco, bv->v->co);
+          copy_v3_v3(pro->middle, bv->v->co);
           if (e->prev->is_bev && e->next->is_bev && bv->selcount >= 3) {
             /* want mid at the meet point of next and prev offset edges */
             float d3[3], d4[3], co4[3], meetco[3], isect2[3];
@@ -1319,7 +1397,7 @@ static void set_profile_params(BevelParams *bp, BevVert *bv, BoundVert *bndv)
             normalize_v3(d4);
             if (nearly_parallel(d3, d4)) {
               /* offset lines are collinear - want linear interpolation */
-              mid_v3_v3v3(pro->midco, co1, co2);
+              mid_v3_v3v3(pro->middle, co1, co2);
               do_linear_interp = true;
             }
             else {
@@ -1327,20 +1405,20 @@ static void set_profile_params(BevelParams *bp, BevVert *bv, BoundVert *bndv)
               add_v3_v3v3(co4, co2, d4);
               isect_kind = isect_line_line_v3(co1, co3, co2, co4, meetco, isect2);
               if (isect_kind != 0) {
-                copy_v3_v3(pro->midco, meetco);
+                copy_v3_v3(pro->middle, meetco);
               }
               else {
                 /* offset lines don't intersect - want linear interpolation */
-                mid_v3_v3v3(pro->midco, co1, co2);
+                mid_v3_v3v3(pro->middle, co1, co2);
                 do_linear_interp = true;
               }
             }
           }
         }
-        copy_v3_v3(pro->cob, co2);
-        sub_v3_v3v3(d1, pro->midco, co1);
+        copy_v3_v3(pro->end, co2);
+        sub_v3_v3v3(d1, pro->middle, co1);
         normalize_v3(d1);
-        sub_v3_v3v3(d2, pro->midco, co2);
+        sub_v3_v3v3(d2, pro->middle, co2);
         normalize_v3(d2);
         cross_v3_v3v3(pro->plane_no, d1, d2);
         normalize_v3(pro->plane_no);
@@ -1357,9 +1435,9 @@ static void set_profile_params(BevelParams *bp, BevVert *bv, BoundVert *bndv)
     copy_v3_v3(pro->plane_co, co1);
   }
   else if (bndv->is_arc_start) {
-    /* assume pro->midco was already set */
-    copy_v3_v3(pro->coa, co1);
-    copy_v3_v3(pro->cob, co2);
+    /* assume pro->middle was alredy set */
+    copy_v3_v3(pro->start, co1);
+    copy_v3_v3(pro->end, co2);
     pro->super_r = PRO_CIRCLE_R;
     zero_v3(pro->plane_co);
     zero_v3(pro->plane_no);
@@ -1368,9 +1446,9 @@ static void set_profile_params(BevelParams *bp, BevVert *bv, BoundVert *bndv)
   }
   if (do_linear_interp) {
     pro->super_r = PRO_LINE_R;
-    copy_v3_v3(pro->coa, co1);
-    copy_v3_v3(pro->cob, co2);
-    mid_v3_v3v3(pro->midco, co1, co2);
+    copy_v3_v3(pro->start, co1);
+    copy_v3_v3(pro->end, co2);
+    mid_v3_v3v3(pro->middle, co1, co2);
     /* won't use projection for this line profile */
     zero_v3(pro->plane_co);
     zero_v3(pro->plane_no);
@@ -1378,22 +1456,23 @@ static void set_profile_params(BevelParams *bp, BevVert *bv, BoundVert *bndv)
   }
 }
 
-/* Maybe move the profile plane for bndv->ebev to the plane its profile's coa, cob and the
+/* Maybe move the profile plane for bndv->ebev to the plane its profile's start,  and the
  * original beveled vert, bmv. This will usually be the plane containing its adjacent
- * non-beveled edges, but sometimes coa and cob are not on those edges.
- */
-static void move_profile_plane(BoundVert *bndv, BMVert *bmv)
+ * non-beveled edges, but sometimes the start and the end are not on those edges.
+ *
+ * Currently just used in build boundary terminal edge */
+static void move_profile_plane(BoundVert *bndv, BMVert *bmvert)
 {
   float d1[3], d2[3], no[3], no2[3], no3[3], dot2, dot3;
   Profile *pro = &bndv->profile;
 
-  /* only do this if projecting, and coa, cob, and proj_dir are not coplanar */
+  /* only do this if projecting, and start, end, and proj_dir are not coplanar */
   if (is_zero_v3(pro->proj_dir)) {
     return;
   }
-  sub_v3_v3v3(d1, bmv->co, pro->coa);
+  sub_v3_v3v3(d1, bmvert->co, pro->start);
   normalize_v3(d1);
-  sub_v3_v3v3(d2, bmv->co, pro->cob);
+  sub_v3_v3v3(d2, bmvert->co, pro->end);
   normalize_v3(d2);
   cross_v3_v3v3(no, d1, d2);
   cross_v3_v3v3(no2, d1, pro->proj_dir);
@@ -1584,35 +1663,35 @@ static double superellipse_co(double x, float r, bool rbig)
 }
 
 /* Find the point on given profile at parameter i which goes from 0 to n as
- * the profile is moved from pro->coa to pro->cob.
+ * the profile is moved from pro->start to pro->end.
  * We assume that n is either the global seg number or a power of 2 less than
  * or equal to the power of 2 >= seg.
  * In the latter case, we subsample the profile for seg_2, which will not necessarily
  * give equal spaced chords, but is in fact more what is desired by the cubic subdivision
  * method used to make the vmesh pattern. */
-static void get_profile_point(BevelParams *bp, const Profile *pro, int i, int n, float r_co[3])
+static void get_profile_point(BevelParams *bp, const Profile *pro, int i, int nseg, float r_co[3])
 {
-  int d;
+  int subsample_spacing;
 
   if (bp->seg == 1) {
     if (i == 0) {
-      copy_v3_v3(r_co, pro->coa);
+      copy_v3_v3(r_co, pro->start);
     }
     else {
-      copy_v3_v3(r_co, pro->cob);
+      copy_v3_v3(r_co, pro->end);
     }
   }
 
   else {
-    if (n == bp->seg) {
+    if (nseg == bp->seg) {
       BLI_assert(pro->prof_co != NULL);
       copy_v3_v3(r_co, pro->prof_co + 3 * i);
     }
     else {
-      BLI_assert(is_power_of_2_i(n) && n <= bp->pro_spacing.seg_2);
-      /* set d to spacing in prof_co_2 between subsamples */
-      d = bp->pro_spacing.seg_2 / n;
-      copy_v3_v3(r_co, pro->prof_co_2 + 3 * i * d);
+      BLI_assert(is_power_of_2_i(nseg) && nseg <= bp->pro_spacing.seg_2);
+      /* Find spacing between subsamples in prof_co_2 */
+      subsample_spacing = bp->pro_spacing.seg_2 / nseg;
+      copy_v3_v3(r_co, pro->prof_co_2 + 3 * i * subsample_spacing);
     }
   }
 }
@@ -1624,15 +1703,16 @@ static void get_profile_point(BevelParams *bp, const Profile *pro, int i, int n,
  * the coordinate values for the power of 2 >= bp->seg,
  * because the ADJ pattern needs power-of-2 boundaries
  * during construction. */
-static void calculate_profile(BevelParams *bp, BoundVert *bndv)
+static void calculate_profile(BevelParams *bp, BoundVert *bndv, bool reversed, bool miter)
 {
   int i, k, ns;
   const double *xvals, *yvals;
-  float co[3], co2[3], p[3], m[4][4];
+  float co[3], co2[3], p[3], map[4][4], bottom_corner[3], top_corner[3];
   float *prof_co, *prof_co_k;
   float r;
   bool need_2, map_ok;
   Profile *pro = &bndv->profile;
+  ProfileSpacing *pro_spacing = (miter) ? &bp->pro_spacing_miter : &bp->pro_spacing;
 
   if (bp->seg == 1) {
     return;
@@ -1640,27 +1720,39 @@ static void calculate_profile(BevelParams *bp, BoundVert *bndv)
 
   need_2 = bp->seg != bp->pro_spacing.seg_2;
   if (!pro->prof_co) {
-    pro->prof_co = (float *)BLI_memarena_alloc(bp->mem_arena, (bp->seg + 1) * 3 * sizeof(float));
+    pro->prof_co = (float *)BLI_memarena_alloc(bp->mem_arena,
+                                               ((size_t)bp->seg + 1) * 3 * sizeof(float));
     if (need_2) {
       pro->prof_co_2 = (float *)BLI_memarena_alloc(
-          bp->mem_arena, (bp->pro_spacing.seg_2 + 1) * 3 * sizeof(float));
+          bp->mem_arena, ((size_t)bp->pro_spacing.seg_2 + 1) * 3 * sizeof(float));
     }
     else {
       pro->prof_co_2 = pro->prof_co;
     }
   }
   r = pro->super_r;
-  if (r == PRO_LINE_R) {
+  if (!bp->use_custom_profile && r == PRO_LINE_R) {
     map_ok = false;
   }
   else {
-    map_ok = make_unit_square_map(pro->coa, pro->midco, pro->cob, m);
-  }
+    map_ok = make_unit_square_map(pro->start, pro->middle, pro->end, map);
+  }
+  if (bp->vmesh_method == BEVEL_VMESH_CUTOFF && map_ok) {
+    /* Calculate the "height" of the profile by putting the (0,0) and (1,1) corners of the
+     * un-transformed profile throught the 2D->3D map and calculating the distance between them */
+    zero_v3(p);
+    mul_v3_m4v3(bottom_corner, map, p);
+    p[0] = 1.0f;
+    p[1] = 1.0f;
+    mul_v3_m4v3(top_corner, map, p);
+    pro->height = len_v3v3(bottom_corner, top_corner);
+  }
+  /* The first iteration is the nseg case, the second is the seg_2 case (if it's needed) */
   for (i = 0; i < 2; i++) {
     if (i == 0) {
       ns = bp->seg;
-      xvals = bp->pro_spacing.xvals;
-      yvals = bp->pro_spacing.yvals;
+      xvals = pro_spacing->xvals;
+      yvals = pro_spacing->yvals;
       prof_co = pro->prof_co;
     }
     else {
@@ -1668,33 +1760,42 @@ static void calculate_profile(BevelParams *bp, BoundVert *bndv)
         break; /* shares coords with pro->prof_co */
       }
       ns = bp->pro_spacing.seg_2;
-      xvals = bp->pro_spacing.xvals_2;
-      yvals = bp->pro_spacing.yvals_2;
+      xvals = pro_spacing->xvals_2;
+      yvals = pro_spacing->yvals_2;
       prof_co = pro->prof_co_2;
     }
-    BLI_assert((r == PRO_LINE_R || (xvals != NULL && yvals != NULL)) && prof_co != NULL);
+
+    /* Iterate over the vertices along the boundary arc */
     for (k = 0; k <= ns; k++) {
       if (k == 0) {
-        copy_v3_v3(co, pro->coa);
+        copy_v3_v3(co, pro->start);
       }
       else if (k == ns) {
-        copy_v3_v3(co, pro->cob);
+        copy_v3_v3(co, pro->end);
       }
       else {
         if (map_ok) {
-          p[0] = xvals[k];
-          p[1] = yvals[k];
+          if (reversed) {
+            p[0] = (float)yvals[ns - k];
+            p[1] = (float)xvals[ns - k];
+          }
+          else {
+            p[0] = (float)xvals[k];
+            p[1] = (float)yvals[k];
+          }
           p[2] = 0.0f;
-          mul_v3_m4v3(co, m, p);
+          /* Do the 2D->3D transformation of the profile coordinates */
+          mul_v3_m4v3(co, map, p);
         }
         else {
-          interp_v3_v3v3(co, pro->coa, pro->cob, (float)k / (float)ns);
+          interp_v3_v3v3(co, pro->start, pro->end, (float)k / (float)ns);
         }
       }
-      /* project co onto final profile plane */
-      prof_co_k = prof_co + 3 * k;
+      /* Finish the 2D->3D transformation by projecting onto the final profile plane */
+      prof_co_k = prof_co + 3 * k; /* Each coord takes up 3 spaces */
       if (!is_zero_v3(pro->proj_dir)) {
         add_v3_v3v3(co2, co, pro->proj_dir);
+        /* pro->plane_co and pro->plane_no are filled in "set_profile_params" */
         if (!isect_line_plane_v3(prof_co_k, co, co2, pro->plane_co, pro->plane_no)) {
           /* shouldn't happen */
           copy_v3_v3(prof_co_k, co);
@@ -1708,11 +1809,12 @@ static void calculate_profile(BevelParams *bp, BoundVert *bndv)
 }
 
 /* Snap a direction co to a superellipsoid with parameter super_r.
- * For square profiles, midline says whether or not to snap to both planes. */
+ * For square profiles, midline says whether or not to snap to both planes.
+ *
+ * Only currently used for the pipe and cube corner special cases */
 static void snap_to_superellipsoid(float co[3], const float super_r, bool midline)
 {
   float a, b, c, x, y, z, r, rinv, dx, dy;
-
   r = super_r;
   if (r == PRO_CIRCLE_R) {
     normalize_v3(co);
@@ -1936,7 +2038,7 @@ static void bevel_extend_edge_data(BevVert *bv)
   } while (bcur != start);
 }
 
-/* Mark edges as sharp if they are between a smooth recon face and a new face. */
+/* Mark edges as sharp if they are between a smooth reconstructed face and a new face. */
 static void bevel_edges_sharp_boundary(BMesh *bm, BevelParams *bp)
 {
   BMIter fiter, liter;
@@ -1972,7 +2074,7 @@ static void bevel_edges_sharp_boundary(BMesh *bm, BevelParams *bp)
  * And at boundaries between #F_EDGE and #F_VERT faces, the normals should match the #F_EDGE ones.
  * Assumes custom loop normals are in use.
  */
-static void bevel_harden_normals(BMesh *bm, BevelParams *bp)
+static void bevel_harden_normals(BevelParams *bp, BMesh *bm)
 {
   BMIter liter, fiter;
   BMFace *f;
@@ -2207,13 +2309,17 @@ static bool eh_on_plane(EdgeHalf *e)
 /* Calculate the profiles for all the BoundVerts of VMesh vm */
 static void calculate_vm_profiles(BevelParams *bp, BevVert *bv, VMesh *vm)
 {
-  BoundVert *v;
+  BoundVert *bndv;
 
-  v = vm->boundstart;
+  bndv = vm->boundstart;
   do {
-    set_profile_params(bp, bv, v);
-    calculate_profile(bp, v);
-  } while ((v = v->next) != vm->boundstart);
+    set_profile_params(bp, bv, bndv);
+    /* Use the miter profile spacing struct if the default is filled with the custom profile. */
+    bool miter_profile = bp->use_custom_profile && (bndv->is_arc_start || bndv->is_patch_start);
+    /* Don't bother reversing the profile if it's a miter profile */
+    bool reverse_profile = !bndv->is_profile_start && !miter_profile;
+    calculate_profile(bp, bndv, reverse_profile, miter_profile);
+  } while ((bndv = bndv->next) != vm->boundstart);
 }
 
 /* Implements build_boundary for vertex-only case */
@@ -2263,14 +2369,15 @@ static void build_boundary_vertex_only(BevelParams *bp, BevVert *bv, bool constr
 static void build_boundary_terminal_edge(BevelParams *bp,
                                          BevVert *bv,
                                          EdgeHalf *efirst,
-                                         bool construct)
+                                         const bool construct)
 {
   MemArena *mem_arena = bp->mem_arena;
   VMesh *vm = bv->vmesh;
-  BoundVert *v;
+  BoundVert *bndv;
   EdgeHalf *e;
   const float *no;
   float co[3], d;
+  bool use_tri_fan;
 
   e = efirst;
   if (bv->edgecount == 2) {
@@ -2278,9 +2385,9 @@ static void build_boundary_terminal_edge(BevelParams *bp,
     no = e->fprev ? e->fprev->no : (e->fnext ? e->fnext->no : NULL);
     offset_in_plane(e, no, true, co);
     if (construct) {
-      v = add_new_bound_vert(mem_arena, vm, co);
-      v->efirst = v->elast = v->ebev = e;
-      e->leftv = v;
+      bndv = add_new_bound_vert(mem_arena, vm, co);
+      bndv->efirst = bndv->elast = bndv->ebev = e;
+      e->leftv = bndv;
     }
     else {
       adjust_bound_vert(e->leftv, co);
@@ -2288,9 +2395,9 @@ static void build_boundary_terminal_edge(BevelParams *bp,
     no = e->fnext ? e->fnext->no : (e->fprev ? e->fprev->no : NULL);
     offset_in_plane(e, no, false, co);
     if (construct) {
-      v = add_new_bound_vert(mem_arena, vm, co);
-      v->efirst = v->elast = e;
-      e->rightv = v;
+      bndv = add_new_bound_vert(mem_arena, vm, co);
+      bndv->efirst = bndv->elast = e;
+      e->rightv = bndv;
     }
     else {
       adjust_bound_vert(e->rightv, co);
@@ -2298,11 +2405,9 @@ static void build_boundary_terminal_edge(BevelParams *bp,
     /* make artificial extra point along unbeveled edge, and form triangle */
     slide_dist(e->next, bv->v, e->offset_l, co);
     if (construct) {
-      v = add_new_bound_vert(mem_arena, vm, co);
-      v->efirst = v->elast = e->next;
-      e->next->leftv = e->next->rightv = v;
-      /* could use M_POLY too, but tri-fan looks nicer)*/
-      vm->mesh_kind = M_TRI_FAN;
+      bndv = add_new_bound_vert(mem_arena, vm, co);
+      bndv->efirst = bndv->elast = e->next;
+      e->next->leftv = e->next->rightv = bndv;
       set_bound_vert_seams(bv, bp->mark_seam, bp->mark_sharp);
     }
     else {
@@ -2316,11 +2421,11 @@ static void build_boundary_terminal_edge(BevelParams *bp,
     /* TODO: should do something else if angle between e and e->prev > 180 */
     offset_meet(e->prev, e, bv->v, e->fprev, false, co);
     if (construct) {
-      v = add_new_bound_vert(mem_arena, vm, co);
-      v->efirst = e->prev;
-      v->elast = v->ebev = e;
-      e->leftv = v;
-      e->prev->leftv = e->prev->rightv = v;
+      bndv = add_new_bound_vert(mem_arena, vm, co);
+      bndv->efirst = e->prev;
+      bndv->elast = bndv->ebev = e;
+      e->leftv = bndv;
+      e->prev->leftv = e->prev->rightv = bndv;
     }
     else {
       adjust_bound_vert(e->leftv, co);
@@ -2328,23 +2433,26 @@ static void build_boundary_terminal_edge(BevelParams *bp,
     e = e->next;
     offset_meet(e->prev, e, bv->v, e->fprev, false, co);
     if (construct) {
-      v = add_new_bound_vert(mem_arena, vm, co);
-      v->efirst = e->prev;
-      v->elast = e;
-      e->leftv = e->rightv = v;
-      e->prev->rightv = v;
+      bndv = add_new_bound_vert(mem_arena, vm, co);
+      bndv->efirst = e->prev;
+      bndv->elast = e;
+      e->leftv = e->rightv = bndv;
+      e->prev->rightv = bndv;
     }
     else {
       adjust_bound_vert(e->leftv, co);
     }
     /* For the edges not adjacent to the beveled edge, slide the bevel amount along. */
     d = efirst->offset_l_spec;
+    if (bp->use_custom_profile || bp->profile < 0.25f) {
+      d *= sqrtf(2.0f); /* Need to go further along the edge to make room for full profile area. */
+    }
     for (e = e->next; e->next != efirst; e = e->next) {
       slide_dist(e, bv->v, d, co);
       if (construct) {
-        v = add_new_bound_vert(mem_arena, vm, co);
-        v->efirst = v->elast = e;
-        e->leftv = e->rightv = v;
+        bndv = add_new_bound_vert(mem_arena, vm, co);
+        bndv->efirst = bndv->elast = e;
+        e->leftv = e->rightv = bndv;
       }
       else {
         adjust_bound_vert(e->leftv, co);
@@ -2354,11 +2462,12 @@ static void build_boundary_terminal_edge(BevelParams *bp,
   calculate_vm_profiles(bp, bv, vm);
 
   if (bv->edgecount >= 3) {
-    /* special case: snap profile to plane of adjacent two edges */
-    v = vm->boundstart;
-    BLI_assert(v->ebev != NULL);
-    move_profile_plane(v, bv->v);
-    calculate_profile(bp, v);
+    /* Special case: snap profile to plane of adjacent two edges. */
+    bndv = vm->boundstart;
+    BLI_assert(bndv->ebev != NULL);
+    move_profile_plane(bndv, bv->v);
+    /* This step happens before the profile orientation pass so don't reverse the profile. */
+    calculate_profile(bp, bndv, false, false);
   }
 
   if (construct) {
@@ -2368,12 +2477,34 @@ static void build_boundary_terminal_edge(BevelParams *bp,
       vm->mesh_kind = M_NONE;
     }
     else if (vm->count == 3) {
-      vm->mesh_kind = M_TRI_FAN;
+      use_tri_fan = true;
+      if (bp->use_custom_profile) {
+        /* Use M_POLY if the extra point is planar with the profile to prevent overhanging edges */
+        bndv = efirst->leftv;
+        float profile_plane[4];
+        plane_from_point_normal_v3(profile_plane, bndv->profile.plane_co, bndv->profile.plane_no);
+        bndv = efirst->rightv->next; /* The added boundvert placed along the non-adjacent edge */
+        if (dist_squared_to_plane_v3(bndv->nv.co, profile_plane) < BEVEL_EPSILON_BIG) {
+          use_tri_fan = false;
+        }
+      }
+      vm->mesh_kind = (use_tri_fan) ? M_TRI_FAN : M_POLY;
     }
     else {
       vm->mesh_kind = M_POLY;
     }
   }
+#ifdef DEBUG_CUSTOM_PROFILE_WELD
+  if (bp->seg > 1) {
+    printf("Terminal Edge Profile Coordinates:\n");
+    for (int k = 0; k < bp->seg; k++) {
+      printf("%0.4f, %0.4f, %0.4f\n",
+             (double)vm->boundstart->profile.prof_co[3 * k],
+             (double)vm->boundstart->profile.prof_co[3 * k + 1],
+             (double)vm->boundstart->profile.prof_co[3 * k + 2]);
+    }
+  }
+#endif
 }
 
 /* Helper for build_boundary to handle special miters */
@@ -2399,7 +2530,7 @@ static void adjust_miter_coords(BevelParams *bp, BevVert *bv, EdgeHalf *emiter)
   v3next = v3->next;
   copy_v3_v3(co2, v1->nv.co);
   if (v1->is_arc_start) {
-    copy_v3_v3(v1->profile.midco, co2);
+    copy_v3_v3(v1->profile.middle, co2);
   }
 
   /* co1 is intersection of line through co2 in dir of emiter->e
@@ -2477,7 +2608,7 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct)
   BoundVert *v, *v1, *v2, *v3;
   VMesh *vm;
   float co[3], r;
-  int nip, nnip, miter_outer, miter_inner;
+  int in_plane, not_in_plane, miter_outer, miter_inner;
   int ang_kind;
 
   /* Current bevel does nothing if only one edge into a vertex */
@@ -2497,7 +2628,7 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct)
   BLI_assert(e->is_bev);
 
   if (bv->selcount == 1) {
-    /* special case: only one beveled edge in */
+    /* Special case: only one beveled edge in */
     build_boundary_terminal_edge(bp, bv, efirst, construct);
     return;
   }
@@ -2509,7 +2640,7 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct)
   /* keep track of the first beveled edge of an outside miter (there can be at most 1 per bv */
   emiter = NULL;
 
-  /* Here: there is more than one beveled edge.
+  /* There is more than one beveled edge.
    * We make BoundVerts to connect the sides of the beveled edges.
    * Non-beveled edges in between will just join to the appropriate juncture point. */
   e = efirst;
@@ -2519,26 +2650,26 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct)
     /* Make the BoundVert for the right side of e; other side will be made
      * when the beveled edge to the left of e is handled.
      * Analyze edges until next beveled edge.
-     * They are either "in plane" (preceding and subsequent faces are coplanar)
-     * or not. The "non-in-plane" edges effect silhouette and we prefer to slide
-     * along one of those if possible. */
-    nip = nnip = 0;    /* counts of in-plane / not-in-plane */
-    enip = eip = NULL; /* representatives of each */
+     * They are either "in plane" (preceding and subsequent faces are coplanar) or not.
+     * The "non-in-plane" edges affect the silhouette and we prefer to slide along one of those if
+     * possible. */
+    in_plane = not_in_plane = 0; /* Counts of in-plane / not-in-plane */
+    enip = eip = NULL;           /* representatives of each */
     for (e2 = e->next; !e2->is_bev; e2 = e2->next) {
       if (eh_on_plane(e2)) {
-        nip++;
+        in_plane++;
         eip = e2;
       }
       else {
-        nnip++;
+        not_in_plane++;
         enip = e2;
       }
     }
-    if (nip == 0 && nnip == 0) {
+    if (in_plane == 0 && not_in_plane == 0) {
       offset_meet(e, e2, bv->v, e->fnext, false, co);
     }
-    else if (nnip > 0) {
-      if (bp->loop_slide && nnip == 1 && good_offset_on_edge_between(e, e2, enip, bv->v)) {
+    else if (not_in_plane > 0) {
+      if (bp->loop_slide && not_in_plane == 1 && good_offset_on_edge_between(e, e2, enip, bv->v)) {
         if (offset_on_edge_between(e, e2, enip, bv->v, co, &r)) {
           eon = enip;
         }
@@ -2548,8 +2679,8 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct)
       }
     }
     else {
-      /* nip > 0 and nnip == 0 */
-      if (bp->loop_slide && nip == 1 && good_offset_on_edge_between(e, e2, eip, bv->v)) {
+      /* n_in_plane > 0 and n_not_in_plane == 0 */
+      if (bp->loop_slide && in_plane == 1 && good_offset_on_edge_between(e, e2, eip, bv->v)) {
         if (offset_on_edge_between(e, e2, eip, bv->v, co, &r)) {
           eon = eip;
         }
@@ -2579,16 +2710,15 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct)
        * There can only be one outer reflex angle, so only one outer miter,
        * and emiter will be set to the first edge of such an edge.
        * A miter kind of BEVEL_MITER_SHARP means no special miter */
-
-      if ((miter_outer != BEVEL_MITER_SHARP && !emiter && ang_kind == 1) ||
-          (miter_inner != BEVEL_MITER_SHARP && ang_kind == -1)) {
-        if (ang_kind == 1) {
+      if ((miter_outer != BEVEL_MITER_SHARP && !emiter && ang_kind == ANGLE_LARGER) ||
+          (miter_inner != BEVEL_MITER_SHARP && ang_kind == ANGLE_SMALLER)) {
+        if (ang_kind == ANGLE_LARGER) {
           emiter = e;
         }
         /* make one or two more boundverts; for now all will have same co */
         v1 = v;
         v1->ebev = NULL;
-        if (ang_kind == 1 && miter_outer == BEVEL_MITER_PATCH) {
+        if (ang_kind == ANGLE_LARGER && miter_outer == BEVEL_MITER_PATCH) {
           v2 = add_new_bound_vert(mem_arena, vm, co);
         }
         else {
@@ -2600,7 +2730,7 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct)
         v3->elast = e2;
         v3->eon = NULL;
         e2->leftv = v3;
-        if (ang_kind == 1 && miter_outer == BEVEL_MITER_PATCH) {
+        if (ang_kind == ANGLE_LARGER && miter_outer == BEVEL_MITER_PATCH) {
           v1->is_patch_start = true;
           v2->eon = v1->eon;
           v2->sinratio = v1->sinratio;
@@ -2622,17 +2752,16 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct)
         }
         else {
           v1->is_arc_start = true;
-          copy_v3_v3(v1->profile.midco, co);
+          copy_v3_v3(v1->profile.middle, co);
           if (e->next == e2) {
             v1->elast = v1->efirst;
           }
           else {
-            int between = nip + nnip;
+            int between = in_plane + not_in_plane;
             int bet2 = between / 2;
             bool betodd = (between % 2) == 1;
             int i = 0;
-            /* Put first half of in-between edges at index 0,
-             * second half at index bp->seg.
+            /* Put first half of in-between edges at index 0, second half at index bp->seg.
              * If between is odd, put middle one at midindex */
             for (e3 = e->next; e3 != e2; e3 = e3->next) {
               v1->elast = e3;
@@ -2651,15 +2780,15 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct)
         }
       }
     }
-    else {
+    else { /* construct == false */
       ang_kind = edges_angle_kind(e, e2, bv->v);
-      if ((miter_outer != BEVEL_MITER_SHARP && !emiter && ang_kind == 1) ||
-          (miter_inner != BEVEL_MITER_SHARP && ang_kind == -1)) {
-        if (ang_kind == 1) {
+      if ((miter_outer != BEVEL_MITER_SHARP && !emiter && ang_kind == ANGLE_LARGER) ||
+          (miter_inner != BEVEL_MITER_SHARP && ang_kind == ANGLE_SMALLER)) {
+        if (ang_kind == ANGLE_LARGER) {
           emiter = e;
         }
         v1 = e->rightv;
-        if (ang_kind == 1 && miter_outer == BEVEL_MITER_PATCH) {
+        if (ang_kind == ANGLE_LARGER && miter_outer == BEVEL_MITER_PATCH) {
           v2 = v1->next;
           v3 = v2->next;
         }
@@ -2699,7 +2828,14 @@ static void build_boundary(BevelParams *bp, BevVert *bv, bool construct)
       vm->mesh_kind = M_POLY;
     }
     else {
-      vm->mesh_kind = M_ADJ;
+      switch (bp->vmesh_method) {
+        case BEVEL_VMESH_ADJ:
+          vm->mesh_kind = M_ADJ;
+          break;
+        case BEVEL_VMESH_CUTOFF:
+          vm->mesh_kind = M_CUTOFF;
+          break;
+      }
     }
   }
 }
@@ -2885,6 +3021,194 @@ static bool adjust_the_cycle_or_chain_fast(BoundVert *vstart, int np, bool iscyc
 }
 #endif
 
+/** Helper function to return the next Beveled EdgeHalf along a path.
+ * \param toward_bv Whether the direction to travel points toward or away from the BevVert
+ *        connected to the current EdgeHalf
+ * \param r_bv The BevVert conencted to the EdgeHalf-- updated if we're travelling to the other
+ *        EdgeHalf of an original edge
+ * \note This only returns the most parallel edge if it's the most parallel by
+ * at least 10 degrees. This is a somewhat arbitrary choice, but it makes sure that consistent
+ * orientation paths only continue in obvious ways. */
+static EdgeHalf *next_edgehalf_bev(BevelParams *bp,
+                                   EdgeHalf *start_edge,
+                                   bool toward_bv,
+                                   BevVert **r_bv)
+{
+  EdgeHalf *new_edge;
+  EdgeHalf *next_edge = NULL;
+  float dir_start_edge[3], dir_new_edge[3];
+  float second_best_dot = 0.0f, best_dot = 0.0f;
+  float new_dot;
+
+  /* Case 1: The next EdgeHalf is across a BevVert from the current EdgeHalf. */
+  if (toward_bv) {
+    /* Skip all the logic if there's only one beveled edge at the vertex, we're at an end. */
+    if ((*r_bv)->selcount == 1) {
+      return NULL; /* No other edges to go to. */
+    }
+
+    /* The case with only one other edge connected to the vertex is special too. */
+    if ((*r_bv)->selcount == 2) {
+      /* Just find the next beveled edge, that's the only other option. */
+      new_edge = start_edge;
+      do {
+        new_edge = new_edge->next;
+      } while (!new_edge->is_bev);
+
+      return new_edge;
+    }
+
+    /* Find the direction vector of the current edge (pointing INTO the BevVert).
+     * v1 and v2 don't necessarily have an order, so we need to check which is closer to bv. */
+    if (start_edge->e->v1 == (*r_bv)->v) {
+      sub_v3_v3v3(dir_start_edge, start_edge->e->v1->co, start_edge->e->v2->co);
+    }
+    else {
+      sub_v3_v3v3(dir_start_edge, start_edge->e->v2->co, start_edge->e->v1->co);
+    }
+    normalize_v3(dir_start_edge);
+
+    /* Find the beveled edge coming out of the BevVert that's most parallel to the current edge. */
+    new_edge = start_edge->next;
+    while (new_edge != start_edge) {
+      if (!new_edge->is_bev) {
+        new_edge = new_edge->next;
+        continue;
+      }
+      /* Find direction vector of the possible next edge (pointing OUT of the BevVert). */
+      if (new_edge->e->v2 == (*r_bv)->v) {
+        sub_v3_v3v3(dir_new_edge, new_edge->e->v1->co, new_edge->e->v2->co);
+      }
+      else {
+        sub_v3_v3v3(dir_new_edge, new_edge->e->v2->co, new_edge->e->v1->co);
+      }
+      normalize_v3(dir_new_edge);
+
+      /* Use this edge if it is the most parallel to the orignial so far */
+      new_dot = dot_v3v3(dir_new_edge, dir_start_edge);
+      if (new_dot > best_dot) {
+        second_best_dot = best_dot; /* For remembering if the choice was too close. */
+        best_dot = new_dot;
+        next_edge = new_edge;
+      }
+      else if (new_dot > second_best_dot) {
+        second_best_dot = new_dot;
+      }
+
+      new_edge = new_edge->next;
+    }
+
+    /* Only return a new Edge if one was found and if the choice of next edge was not too close. */
+    if ((next_edge != NULL) && compare_ff(best_dot, second_best_dot, BEVEL_SMALL_ANG_DOT)) {
+      return NULL;
+    }
+    else {
+      return next_edge;
+    }
+  }
+  else {
+    /* Case 2: The next EdgeHalf is the other side of the BMEdge.
+     * It's part of the same BMEdge, so we know the other EdgeHalf is also beveled. */
+    next_edge = find_other_end_edge_half(bp, start_edge, r_bv);
+    return next_edge;
+  }
+}
+
+/** Starting along any beveled edge, travel along the chain / cycle of beveled edges including that
+ * edge, marking consistent profile orientations along the way. Orientations are marked by setting
+ * whether the BoundVert that contains each profile's information is the side of the profile's
+ * start or not. */
+static void regularize_profile_orientation(BevelParams *bp, BMEdge *bme)
+{
+  BevVert *start_bv;
+  BevVert *bv;
+  EdgeHalf *start_edgehalf, *edgehalf;
+  bool toward_bv;
+
+  start_bv = find_bevvert(bp, bme->v1);
+  start_edgehalf = find_edge_half(start_bv, bme);
+  if (!start_edgehalf->is_bev || start_edgehalf->visited_rpo) {
+    return;
+  }
+
+  /* Pick a BoundVert on one side of the profile to use for the start of the profile. */
+  start_edgehalf->leftv->is_profile_start = false;
+  start_edgehalf->visited_rpo = true;
+
+  /* First loop starts in the away from BevVert direction and the second starts toward it. */
+  for (int i = 0; i < 2; i++) {
+    edgehalf = start_edgehalf;
+    bv = start_bv;
+    toward_bv = (i == 0);
+    edgehalf = next_edgehalf_bev(bp, edgehalf, toward_bv, &bv);
+
+    /* Keep traveling until there is no unvisited beveled edgehalf to visit next. */
+    while (edgehalf && !edgehalf->visited_rpo) {
+      /* Mark the correct BoundVert as the start of the newly visited profile.
+       * The direction relative to the BevVert switches every step, so also switch
+       * the orientation every step. */
+      if (i == 0) {
+        edgehalf->leftv->is_profile_start = toward_bv;
+      }
+      else {
+        /* The opposite side as the first direction because we're moving the other way. */
+        edgehalf->leftv->is_profile_start = !toward_bv;
+      }
+
+      /* The next jump will in the opposite direction relative to the BevVert. */
+      toward_bv = !toward_bv;
+
+      edgehalf->visited_rpo = true;
+      edgehalf = next_edgehalf_bev(bp, edgehalf, toward_bv, &bv);
+    }
+  }
+}
+
+#ifdef DEBUG_PROFILE_ORIENTATION_DRAW
+/** Draws markers on beveled edges showing the side that the profile starts on. A sphere shows
+ * the start side of the profile where it starts, and the lines connected to the sphere show which
+ * edge the orientation corresponds to.
+ * \note Only drawn while bevel is calculating, the debug geometry is not persistent. */
+static void debug_draw_profile_orientation(BevelParams *bp, BMesh *bm)
+{
+  BMIter iter;
+  BMEdge *bmedge;
+  float middle[3];
+
+  BM_ITER_MESH (bmedge, &iter, bm, BM_EDGES_OF_MESH) {
+    if (BM_elem_flag_test(bmedge, BM_ELEM_TAG)) {
+      mid_v3_v3v3(middle, bmedge->v1->co, bmedge->v2->co);
+
+      /* Draw the orientation for the first side of the edge. */
+      EdgeHalf *edge_half = find_edge_half(find_bevvert(bp, bmedge->v1), bmedge);
+      if (edge_half->leftv->is_profile_start) { /* The left boundvert defines the profiles. */
+        DRW_debug_sphere(edge_half->leftv->nv.co, 0.04f, debug_color_red);
+        DRW_debug_line_v3v3(middle, edge_half->leftv->nv.co, debug_color_red);
+        DRW_debug_line_v3v3(bmedge->v1->co, edge_half->leftv->nv.co, debug_color_red);
+      }
+      else {
+        DRW_debug_sphere(edge_half->rightv->nv.co, 0.04f, debug_color_red);
+        DRW_debug_line_v3v3(middle, edge_half->rightv->nv.co, debug_color_red);
+        DRW_debug_line_v3v3(bmedge->v1->co, edge_half->rightv->nv.co, debug_color_red);
+      }
+
+      /* Draw the orientation for the second side of the edge. */
+      edge_half = find_edge_half(find_bevvert(bp, bmedge->v2), bmedge);
+      if (edge_half->leftv->is_profile_start) {
+        DRW_debug_sphere(edge_half->leftv->nv.co, 0.05f, debug_color_blue);
+        DRW_debug_line_v3v3(middle, edge_half->leftv->nv.co, debug_color_blue);
+        DRW_debug_line_v3v3(bmedge->v2->co, edge_half->leftv->nv.co, debug_color_blue);
+      }
+      else {
+        DRW_debug_sphere(edge_half->rightv->nv.co, 0.05f, debug_color_blue);
+        DRW_debug_line_v3v3(middle, edge_half->rightv->nv.co, debug_color_blue);
+        DRW_debug_line_v3v3(bmedge->v2->co, edge_half->rightv->nv.co, debug_color_blue);
+      }
+    }
+  }
+}
+#endif
+
 /* Adjust the offsets for a single cycle or chain.
  * For chains and some cycles, a fast solution exists.
  * Otherwise, we set up and solve a linear least squares problem
@@ -3218,8 +3542,8 @@ static VMesh *new_adj_vmesh(MemArena *mem_arena, int count, int seg, BoundVert *
   vm->count = count;
   vm->seg = seg;
   vm->boundstart = bounds;
-  vm->mesh = (NewVert *)BLI_memarena_alloc(mem_arena,
-                                           count * (1 + seg / 2) * (1 + seg) * sizeof(NewVert));
+  vm->mesh = (NewVert *)BLI_memarena_alloc(
+      mem_arena, (size_t)(count * (1 + seg / 2) * (1 + seg)) * sizeof(NewVert));
   vm->mesh_kind = M_ADJ;
   return vm;
 }
@@ -3261,10 +3585,10 @@ static NewVert *mesh_vert_canon(VMesh *vm, int i, int j, int k)
 static bool is_canon(VMesh *vm, int i, int j, int k)
 {
   int ns2 = vm->seg / 2;
-  if (vm->seg % 2 == 1) {
+  if (vm->seg % 2 == 1) { /* odd */
     return (j <= ns2 && k <= ns2);
   }
-  else {
+  else { /* even */
     return ((j < ns2 && k <= ns2) || (j == ns2 && k == ns2 && i == 0));
   }
 }
@@ -3296,6 +3620,9 @@ static void vmesh_copy_equiv_verts(VMesh *vm)
 /* Calculate and return in r_cent the centroid of the center poly */
 static void vmesh_center(VMesh *vm, float r_cent[3])
 {
+#ifdef DEBUG_CUSTOM_PROFILE_ADJ
+  printf("VMESH CENTER\n");
+#endif
   int n, ns2, i;
 
   n = vm->count;
@@ -3432,182 +3759,193 @@ static int interp_range(const float *frac, int n, const float f, float *r_rest)
 }
 
 /* Interpolate given vmesh to make one with target nseg border vertices on the profiles */
-static VMesh *interp_vmesh(BevelParams *bp, VMesh *vm0, int nseg)
+/* HANS-TODO: This puts the center mesh vert at a slightly off location sometimes, which seems to
+ * be associated with the rest of that ring being shifted or connected slightly incorrectly to its
+ * neighbors */
+static VMesh *interp_vmesh(BevelParams *bp, VMesh *vm_in, int nseg)
 {
-  int n, ns0, nseg2, odd, i, j, k, j0, k0, k0prev, j0inc, k0inc;
+  int n_bndv, ns_in, nseg2, odd, i, j, k, j_in, k_in, k_in_prev, j0inc, k0inc;
   float *prev_frac, *frac, *new_frac, *prev_new_frac;
-  float f, restj, restk, restkprev;
+  float fraction, restj, restk, restkprev;
   float quad[4][3], co[3], center[3];
-  VMesh *vm1;
+  VMesh *vm_out;
   BoundVert *bndv;
 
-  n = vm0->count;
-  ns0 = vm0->seg;
+  n_bndv = vm_in->count;
+  ns_in = vm_in->seg;
   nseg2 = nseg / 2;
   odd = nseg % 2;
-  vm1 = new_adj_vmesh(bp->mem_arena, n, nseg, vm0->boundstart);
+  vm_out = new_adj_vmesh(bp->mem_arena, n_bndv, nseg, vm_in->boundstart);
 
-  prev_frac = BLI_array_alloca(prev_frac, (ns0 + 1));
-  frac = BLI_array_alloca(frac, (ns0 + 1));
+  prev_frac = BLI_array_alloca(prev_frac, (ns_in + 1));
+  frac = BLI_array_alloca(frac, (ns_in + 1));
   new_frac = BLI_array_alloca(new_frac, (nseg + 1));
   prev_new_frac = BLI_array_alloca(prev_new_frac, (nseg + 1));
 
-  fill_vmesh_fracs(vm0, prev_frac, n - 1);
-  bndv = vm0->boundstart;
+  fill_vmesh_fracs(vm_in, prev_frac, n_bndv - 1);
+  bndv = vm_in->boundstart;
   fill_profile_fracs(bp, bndv->prev, prev_new_frac, nseg);
-  for (i = 0; i < n; i++) {
-    fill_vmesh_fracs(vm0, frac, i);
+  for (i = 0; i < n_bndv; i++) {
+    fill_vmesh_fracs(vm_in, frac, i);
     fill_profile_fracs(bp, bndv, new_frac, nseg);
     for (j = 0; j <= nseg2 - 1 + odd; j++) {
       for (k = 0; k <= nseg2; k++) {
-        f = new_frac[k];
-        k0 = interp_range(frac, ns0, f, &restk);
-        f = prev_new_frac[nseg - j];
-        k0prev = interp_range(prev_frac, ns0, f, &restkprev);
-        j0 = ns0 - k0prev;
+        /* Finding the locations where "fraction" fits into previous and current "frac" */
+        fraction = new_frac[k];
+        k_in = interp_range(frac, ns_in, fraction, &restk);
+        fraction = prev_new_frac[nseg - j];
+        k_in_prev = interp_range(prev_frac, ns_in, fraction, &restkprev);
+        j_in = ns_in - k_in_prev;
         restj = -restkprev;
         if (restj > -BEVEL_EPSILON) {
           restj = 0.0f;
         }
         else {
-          j0 = j0 - 1;
+          j_in = j_in - 1;
           restj = 1.0f + restj;
         }
         /* Use bilinear interpolation within the source quad; could be smarter here */
         if (restj < BEVEL_EPSILON && restk < BEVEL_EPSILON) {
-          copy_v3_v3(co, mesh_vert_canon(vm0, i, j0, k0)->co);
+          copy_v3_v3(co, mesh_vert_canon(vm_in, i, j_in, k_in)->co);
         }
         else {
-          j0inc = (restj < BEVEL_EPSILON || j0 == ns0) ? 0 : 1;
-          k0inc = (restk < BEVEL_EPSILON || k0 == ns0) ? 0 : 1;
-          copy_v3_v3(quad[0], mesh_vert_canon(vm0, i, j0, k0)->co);
-          copy_v3_v3(quad[1], mesh_vert_canon(vm0, i, j0, k0 + k0inc)->co);
-          copy_v3_v3(quad[2], mesh_vert_canon(vm0, i, j0 + j0inc, k0 + k0inc)->co);
-          copy_v3_v3(quad[3], mesh_vert_canon(vm0, i, j0 + j0inc, k0)->co);
+          j0inc = (restj < BEVEL_EPSILON || j_in == ns_in) ? 0 : 1;
+          k0inc = (restk < BEVEL_EPSILON || k_in == ns_in) ? 0 : 1;
+          copy_v3_v3(quad[0], mesh_vert_canon(vm_in, i, j_in, k_in)->co);
+          copy_v3_v3(quad[1], mesh_vert_canon(vm_in, i, j_in, k_in + k0inc)->co);
+          copy_v3_v3(quad[2], mesh_vert_canon(vm_in, i, j_in + j0inc, k_in + k0inc)->co);
+          copy_v3_v3(quad[3], mesh_vert_canon(vm_in, i, j_in + j0inc, k_in)->co);
           interp_bilinear_quad_v3(quad, restk, restj, co);
         }
-        copy_v3_v3(mesh_vert(vm1, i, j, k)->co, co);
+        copy_v3_v3(mesh_vert(vm_out, i, j, k)->co, co);
       }
     }
     bndv = bndv->next;
-    memcpy(prev_frac, frac, (ns0 + 1) * sizeof(float));
-    memcpy(prev_new_frac, new_frac, (nseg + 1) * sizeof(float));
+    memcpy(prev_frac, frac, (size_t)(ns_in + 1) * sizeof(float));
+    memcpy(prev_new_frac, new_frac, (size_t)(nseg + 1) * sizeof(float));
   }
   if (!odd) {
-    vmesh_center(vm0, center);
-    copy_v3_v3(mesh_vert(vm1, 0, nseg2, nseg2)->co, center);
+    vmesh_center(vm_in, center);
+    copy_v3_v3(mesh_vert(vm_out, 0, nseg2, nseg2)->co, center);
   }
-  vmesh_copy_equiv_verts(vm1);
-  return vm1;
+  vmesh_copy_equiv_verts(vm_out);
+  return vm_out;
 }
 
 /* Do one step of cubic subdivision (Catmull-Clark), with special rules at boundaries.
  * For now, this is written assuming vm0->nseg is even and > 0.
- * We are allowed to modify vm0, as it will not be used after this call.
+ * We are allowed to modify vm_in, as it will not be used after this call.
  * See Levin 1999 paper: "Filling an N-sided hole using combined subdivision schemes". */
-static VMesh *cubic_subdiv(BevelParams *bp, VMesh *vm0)
+static VMesh *cubic_subdiv(BevelParams *bp, VMesh *vm_in)
 {
-  int n, ns0, ns20, ns1;
+  int n_boundary, ns_in, ns_in2, ns_out;
   int i, j, k, inext;
   float co[3], co1[3], co2[3], acc[3];
   float beta, gamma;
-  VMesh *vm1;
+  VMesh *vm_out;
   BoundVert *bndv;
 
-  n = vm0->count;
-  ns0 = vm0->seg;
-  ns20 = ns0 / 2;
-  BLI_assert(ns0 % 2 == 0);
-  ns1 = 2 * ns0;
-  vm1 = new_adj_vmesh(bp->mem_arena, n, ns1, vm0->boundstart);
+  n_boundary = vm_in->count;
+  ns_in = vm_in->seg;
+  ns_in2 = ns_in / 2;
+  BLI_assert(ns_in % 2 == 0);
+  ns_out = 2 * ns_in;
+  vm_out = new_adj_vmesh(bp->mem_arena, n_boundary, ns_out, vm_in->boundstart);
 
-  /* First we adjust the boundary vertices of the input mesh, storing in output mesh */
-  for (i = 0; i < n; i++) {
-    copy_v3_v3(mesh_vert(vm1, i, 0, 0)->co, mesh_vert(vm0, i, 0, 0)->co);
-    for (k = 1; k < ns0; k++) {
-      /* smooth boundary rule */
-      copy_v3_v3(co, mesh_vert(vm0, i, 0, k)->co);
-      copy_v3_v3(co1, mesh_vert(vm0, i, 0, k - 1)->co);
-      copy_v3_v3(co2, mesh_vert(vm0, i, 0, k + 1)->co);
+  /* First we adjust the boundary vertices of the input mesh, storing in output mesh. */
+  for (i = 0; i < n_boundary; i++) {
+    copy_v3_v3(mesh_vert(vm_out, i, 0, 0)->co, mesh_vert(vm_in, i, 0, 0)->co);
+    for (k = 1; k < ns_in; k++) {
+      copy_v3_v3(co, mesh_vert(vm_in, i, 0, k)->co);
 
-      add_v3_v3v3(acc, co1, co2);
-      madd_v3_v3fl(acc, co, -2.0f);
-      madd_v3_v3fl(co, acc, -1.0f / 6.0f);
+      /* Smooth boundary rule. Custom profiles shouldn't be smoothed. */
+      if (!bp->use_custom_profile) {
+        copy_v3_v3(co1, mesh_vert(vm_in, i, 0, k - 1)->co);
+        copy_v3_v3(co2, mesh_vert(vm_in, i, 0, k + 1)->co);
 
-      copy_v3_v3(mesh_vert_canon(vm1, i, 0, 2 * k)->co, co);
+        add_v3_v3v3(acc, co1, co2);
+        madd_v3_v3fl(acc, co, -2.0f);
+        madd_v3_v3fl(co, acc, -1.0f / 6.0f);
+      }
+
+      copy_v3_v3(mesh_vert_canon(vm_out, i, 0, 2 * k)->co, co);
     }
   }
-  /* now do odd ones in output mesh, based on even ones */
-  bndv = vm1->boundstart;
-  for (i = 0; i < n; i++) {
-    for (k = 1; k < ns1; k += 2) {
-      get_profile_point(bp, &bndv->profile, k, ns1, co);
-      copy_v3_v3(co1, mesh_vert_canon(vm1, i, 0, k - 1)->co);
-      copy_v3_v3(co2, mesh_vert_canon(vm1, i, 0, k + 1)->co);
+  /* Now adjust odd boundary vertices in output mesh, based on even ones. */
+  bndv = vm_out->boundstart;
+  for (i = 0; i < n_boundary; i++) {
+    for (k = 1; k < ns_out; k += 2) {
+      get_profile_point(bp, &bndv->profile, k, ns_out, co);
 
-      add_v3_v3v3(acc, co1, co2);
-      madd_v3_v3fl(acc, co, -2.0f);
-      madd_v3_v3fl(co, acc, -1.0f / 6.0f);
+      /* Smooth if using a non-custom profile. */
+      if (!bp->use_custom_profile) {
+        copy_v3_v3(co1, mesh_vert_canon(vm_out, i, 0, k - 1)->co);
+        copy_v3_v3(co2, mesh_vert_canon(vm_out, i, 0, k + 1)->co);
 
-      copy_v3_v3(mesh_vert_canon(vm1, i, 0, k)->co, co);
+        add_v3_v3v3(acc, co1, co2);
+        madd_v3_v3fl(acc, co, -2.0f);
+        madd_v3_v3fl(co, acc, -1.0f / 6.0f);
+      }
+
+      copy_v3_v3(mesh_vert_canon(vm_out, i, 0, k)->co, co);
     }
     bndv = bndv->next;
   }
-  vmesh_copy_equiv_verts(vm1);
+  vmesh_copy_equiv_verts(vm_out);
 
-  /* Copy adjusted verts back into vm0 */
-  for (i = 0; i < n; i++) {
-    for (k = 0; k < ns0; k++) {
-      copy_v3_v3(mesh_vert(vm0, i, 0, k)->co, mesh_vert(vm1, i, 0, 2 * k)->co);
+  /* Copy adjusted verts back into vm_in. */
+  for (i = 0; i < n_boundary; i++) {
+    for (k = 0; k < ns_in; k++) {
+      copy_v3_v3(mesh_vert(vm_in, i, 0, k)->co, mesh_vert(vm_out, i, 0, 2 * k)->co);
     }
   }
 
-  vmesh_copy_equiv_verts(vm0);
+  vmesh_copy_equiv_verts(vm_in);
 
   /* Now we do the internal vertices, using standard Catmull-Clark
    * and assuming all boundary vertices have valence 4 */
 
   /* The new face vertices */
-  for (i = 0; i < n; i++) {
-    for (j = 0; j < ns20; j++) {
-      for (k = 0; k < ns20; k++) {
+  for (i = 0; i < n_boundary; i++) {
+    for (j = 0; j < ns_in2; j++) {
+      for (k = 0; k < ns_in2; k++) {
         /* face up and right from (j, k) */
         avg4(co,
-             mesh_vert(vm0, i, j, k),
-             mesh_vert(vm0, i, j, k + 1),
-             mesh_vert(vm0, i, j + 1, k),
-             mesh_vert(vm0, i, j + 1, k + 1));
-        copy_v3_v3(mesh_vert(vm1, i, 2 * j + 1, 2 * k + 1)->co, co);
+             mesh_vert(vm_in, i, j, k),
+             mesh_vert(vm_in, i, j, k + 1),
+             mesh_vert(vm_in, i, j + 1, k),
+             mesh_vert(vm_in, i, j + 1, k + 1));
+        copy_v3_v3(mesh_vert(vm_out, i, 2 * j + 1, 2 * k + 1)->co, co);
       }
     }
   }
 
-  /* The new vertical edge vertices  */
-  for (i = 0; i < n; i++) {
-    for (j = 0; j < ns20; j++) {
-      for (k = 1; k <= ns20; k++) {
+  /* The new vertical edge vertices */
+  for (i = 0; i < n_boundary; i++) {
+    for (j = 0; j < ns_in2; j++) {
+      for (k = 1; k <= ns_in2; k++) {
         /* vertical edge between (j, k) and (j+1, k) */
         avg4(co,
-             mesh_vert(vm0, i, j, k),
-             mesh_vert(vm0, i, j + 1, k),
-             mesh_vert_canon(vm1, i, 2 * j + 1, 2 * k - 1),
-             mesh_vert_canon(vm1, i, 2 * j + 1, 2 * k + 1));
-        copy_v3_v3(mesh_vert(vm1, i, 2 * j + 1, 2 * k)->co, co);
+             mesh_vert(vm_in, i, j, k),
+             mesh_vert(vm_in, i, j + 1, k),
+             mesh_vert_canon(vm_out, i, 2 * j + 1, 2 * k - 1),
+             mesh_vert_canon(vm_out, i, 2 * j + 1, 2 * k + 1));
+        copy_v3_v3(mesh_vert(vm_out, i, 2 * j + 1, 2 * k)->co, co);
       }
     }
   }
 
-  /* The new horizontal edge vertices  */
-  for (i = 0; i < n; i++) {
-    for (j = 1; j < ns20; j++) {
-      for (k = 0; k < ns20; k++) {
+  /* The new horizontal edge vertices */
+  for (i = 0; i < n_boundary; i++) {
+    for (j = 1; j < ns_in2; j++) {
+      for (k = 0; k < ns_in2; k++) {
         /* horizontal edge between (j, k) and (j, k+1) */
         avg4(co,
-             mesh_vert(vm0, i, j, k),
-             mesh_vert(vm0, i, j, k + 1),
-             mesh_vert_canon(vm1, i, 2 * j - 1, 2 * k + 1),
-             mesh_vert_canon(vm1, i, 2 * j + 1, 2 * k + 1));
-        copy_v3_v3(mesh_vert(vm1, i, 2 * j, 2 * k + 1)->co, co);
+             mesh_vert(vm_in, i, j, k),
+             mesh_vert(vm_in, i, j, k + 1),
+             mesh_vert_canon(vm_out, i, 2 * j - 1, 2 * k + 1),
+             mesh_vert_canon(vm_out, i, 2 * j + 1, 2 * k + 1));
+        copy_v3_v3(mesh_vert(vm_out, i, 2 * j, 2 * k + 1)->co, co);
       }
     }
   }
@@ -3615,66 +3953,66 @@ static VMesh *cubic_subdiv(BevelParams *bp, VMesh *vm0)
   /* The new vertices, not on border */
   gamma = 0.25f;
   beta = -gamma;
-  for (i = 0; i < n; i++) {
-    for (j = 1; j < ns20; j++) {
-      for (k = 1; k <= ns20; k++) {
+  for (i = 0; i < n_boundary; i++) {
+    for (j = 1; j < ns_in2; j++) {
+      for (k = 1; k <= ns_in2; k++) {
         /* co1 = centroid of adjacent new edge verts */
         avg4(co1,
-             mesh_vert_canon(vm1, i, 2 * j, 2 * k - 1),
-             mesh_vert_canon(vm1, i, 2 * j, 2 * k + 1),
-             mesh_vert_canon(vm1, i, 2 * j - 1, 2 * k),
-             mesh_vert_canon(vm1, i, 2 * j + 1, 2 * k));
+             mesh_vert_canon(vm_out, i, 2 * j, 2 * k - 1),
+             mesh_vert_canon(vm_out, i, 2 * j, 2 * k + 1),
+             mesh_vert_canon(vm_out, i, 2 * j - 1, 2 * k),
+             mesh_vert_canon(vm_out, i, 2 * j + 1, 2 * k));
         /* co2 = centroid of adjacent new face verts */
         avg4(co2,
-             mesh_vert_canon(vm1, i, 2 * j - 1, 2 * k - 1),
-             mesh_vert_canon(vm1, i, 2 * j + 1, 2 * k - 1),
-             mesh_vert_canon(vm1, i, 2 * j - 1, 2 * k + 1),
-             mesh_vert_canon(vm1, i, 2 * j + 1, 2 * k + 1));
+             mesh_vert_canon(vm_out, i, 2 * j - 1, 2 * k - 1),
+             mesh_vert_canon(vm_out, i, 2 * j + 1, 2 * k - 1),
+             mesh_vert_canon(vm_out, i, 2 * j - 1, 2 * k + 1),
+             mesh_vert_canon(vm_out, i, 2 * j + 1, 2 * k + 1));
         /* combine with original vert with alpha, beta, gamma factors */
         copy_v3_v3(co, co1); /* alpha = 1.0 */
         madd_v3_v3fl(co, co2, beta);
-        madd_v3_v3fl(co, mesh_vert(vm0, i, j, k)->co, gamma);
-        copy_v3_v3(mesh_vert(vm1, i, 2 * j, 2 * k)->co, co);
+        madd_v3_v3fl(co, mesh_vert(vm_in, i, j, k)->co, gamma);
+        copy_v3_v3(mesh_vert(vm_out, i, 2 * j, 2 * k)->co, co);
       }
     }
   }
 
-  vmesh_copy_equiv_verts(vm1);
+  vmesh_copy_equiv_verts(vm_out);
 
   /* The center vertex is special */
-  gamma = sabin_gamma(n);
+  gamma = sabin_gamma(n_boundary);
   beta = -gamma;
   /* accumulate edge verts in co1, face verts in co2 */
   zero_v3(co1);
   zero_v3(co2);
-  for (i = 0; i < n; i++) {
-    add_v3_v3(co1, mesh_vert(vm1, i, ns0, ns0 - 1)->co);
-    add_v3_v3(co2, mesh_vert(vm1, i, ns0 - 1, ns0 - 1)->co);
-    add_v3_v3(co2, mesh_vert(vm1, i, ns0 - 1, ns0 + 1)->co);
+  for (i = 0; i < n_boundary; i++) {
+    add_v3_v3(co1, mesh_vert(vm_out, i, ns_in, ns_in - 1)->co);
+    add_v3_v3(co2, mesh_vert(vm_out, i, ns_in - 1, ns_in - 1)->co);
+    add_v3_v3(co2, mesh_vert(vm_out, i, ns_in - 1, ns_in + 1)->co);
   }
   copy_v3_v3(co, co1);
-  mul_v3_fl(co, 1.0f / (float)n);
-  madd_v3_v3fl(co, co2, beta / (2.0f * (float)n));
-  madd_v3_v3fl(co, mesh_vert(vm0, 0, ns20, ns20)->co, gamma);
-  for (i = 0; i < n; i++) {
-    copy_v3_v3(mesh_vert(vm1, i, ns0, ns0)->co, co);
+  mul_v3_fl(co, 1.0f / (float)n_boundary);
+  madd_v3_v3fl(co, co2, beta / (2.0f * (float)n_boundary));
+  madd_v3_v3fl(co, mesh_vert(vm_in, 0, ns_in2, ns_in2)->co, gamma);
+  for (i = 0; i < n_boundary; i++) {
+    copy_v3_v3(mesh_vert(vm_out, i, ns_in, ns_in)->co, co);
   }
 
-  /* Final step: sample the boundary vertices at even parameter spacing */
-  bndv = vm1->boundstart;
-  for (i = 0; i < n; i++) {
-    inext = (i + 1) % n;
-    for (k = 0; k <= ns1; k++) {
-      get_profile_point(bp, &bndv->profile, k, ns1, co);
-      copy_v3_v3(mesh_vert(vm1, i, 0, k)->co, co);
-      if (k >= ns0 && k < ns1) {
-        copy_v3_v3(mesh_vert(vm1, inext, ns1 - k, 0)->co, co);
+  /* Final step: Copy the profile vertices to the VMesh's boundary */
+  bndv = vm_out->boundstart;
+  for (i = 0; i < n_boundary; i++) {
+    inext = (i + 1) % n_boundary;
+    for (k = 0; k <= ns_out; k++) {
+      get_profile_point(bp, &bndv->profile, k, ns_out, co);
+      copy_v3_v3(mesh_vert(vm_out, i, 0, k)->co, co);
+      if (k >= ns_in && k < ns_out) {
+        copy_v3_v3(mesh_vert(vm_out, inext, ns_out - k, 0)->co, co);
       }
     }
     bndv = bndv->next;
   }
 
-  return vm1;
+  return vm_out;
 }
 
 /* Special case for cube corner, when r is PRO_SQUARE_R, meaning straight sides */
@@ -3686,7 +4024,7 @@ static VMesh *make_cube_corner_square(MemArena *mem_arena, int nseg)
 
   ns2 = nseg / 2;
   vm = new_adj_vmesh(mem_arena, 3, nseg, NULL);
-  vm->count = 0;  // reset, so following loop will end up with correct count
+  vm->count = 0; /* Reset, so the following loop will end up with correct count. */
   for (i = 0; i < 3; i++) {
     zero_v3(co);
     co[i] = 1.0f;
@@ -3765,16 +4103,18 @@ static VMesh *make_cube_corner_adj_vmesh(BevelParams *bp)
   int i, j, k, ns2;
   float co[3], coc[3];
 
-  if (r == PRO_SQUARE_R) {
-    return make_cube_corner_square(mem_arena, nseg);
-  }
-  else if (r == PRO_SQUARE_IN_R) {
-    return make_cube_corner_square_in(mem_arena, nseg);
+  if (!bp->use_custom_profile) {
+    if (r == PRO_SQUARE_R) {
+      return make_cube_corner_square(mem_arena, nseg);
+    }
+    else if (r == PRO_SQUARE_IN_R) {
+      return make_cube_corner_square_in(mem_arena, nseg);
+    }
   }
 
-  /* initial mesh has 3 sides, 2 segments */
+  /* Initial mesh has 3 sides and 2 segments on each side */
   vm0 = new_adj_vmesh(mem_arena, 3, 2, NULL);
-  vm0->count = 0;  // reset, so following loop will end up with correct count
+  vm0->count = 0; /* Reset, so the following loop will end up with correct count. */
   for (i = 0; i < 3; i++) {
     zero_v3(co);
     co[i] = 1.0f;
@@ -3787,20 +4127,24 @@ static VMesh *make_cube_corner_adj_vmesh(BevelParams *bp)
     coc[(i + 1) % 3] = 1.0f;
     coc[(i + 2) % 3] = 0.0f;
     bndv->profile.super_r = r;
-    copy_v3_v3(bndv->profile.coa, bndv->nv.co);
-    copy_v3_v3(bndv->profile.cob, bndv->next->nv.co);
-    copy_v3_v3(bndv->profile.midco, coc);
-    copy_v3_v3(mesh_vert(vm0, i, 0, 0)->co, bndv->profile.coa);
-    copy_v3_v3(bndv->profile.plane_co, bndv->profile.coa);
-    cross_v3_v3v3(bndv->profile.plane_no, bndv->profile.coa, bndv->profile.cob);
+    copy_v3_v3(bndv->profile.start, bndv->nv.co);
+    copy_v3_v3(bndv->profile.end, bndv->next->nv.co);
+    copy_v3_v3(bndv->profile.middle, coc);
+    copy_v3_v3(mesh_vert(vm0, i, 0, 0)->co, bndv->profile.start);
+    copy_v3_v3(bndv->profile.plane_co, bndv->profile.start);
+    cross_v3_v3v3(bndv->profile.plane_no, bndv->profile.start, bndv->profile.end);
     copy_v3_v3(bndv->profile.proj_dir, bndv->profile.plane_no);
-    calculate_profile(bp, bndv);
+    /* No need to reverse the profile or use the miter profile spacing struct because this case
+     * isn't used with custom profiles. */
+    calculate_profile(bp, bndv, false, false);
+
+    /* Just building the boundaries here, so sample the profile halfway through */
     get_profile_point(bp, &bndv->profile, 1, 2, mesh_vert(vm0, i, 0, 1)->co);
 
     bndv = bndv->next;
   }
   /* center vertex */
-  copy_v3_fl(co, M_SQRT1_3);
+  copy_v3_fl(co, (float)M_SQRT1_3);
 
   if (nseg > 2) {
     if (r > 1.5f) {
@@ -3843,7 +4187,8 @@ static int tri_corner_test(BevelParams *bp, BevVert *bv)
   int i;
   int in_plane_e = 0;
 
-  if (bp->vertex_only) {
+  /* The superellipse snapping of this case isn't helpful with custom profiles enabled. */
+  if (bp->vertex_only || bp->use_custom_profile) {
     return -1;
   }
   if (bv->vmesh->count != 3) {
@@ -3909,94 +4254,71 @@ static VMesh *tri_corner_adj_vmesh(BevelParams *bp, BevVert *bv)
   return vm;
 }
 
+/* Makes the mesh that replaces the original vertex, bounded by the profiles on the sides */
 static VMesh *adj_vmesh(BevelParams *bp, BevVert *bv)
 {
-  int n, ns, i;
+  int n_bndv, nseg, i;
   VMesh *vm0, *vm1;
-  float co[3], coa[3], cob[3], dir[3];
+  float boundverts_center[3], original_vertex[3], negative_fullest[3], center_direction[3];
   BoundVert *bndv;
   MemArena *mem_arena = bp->mem_arena;
-  float r, p, fullness;
-  /* best fullness for circles, segs = 2,4,6,8,10 */
-#define CIRCLE_FULLNESS_SEGS 11
-  static const float circle_fullness[CIRCLE_FULLNESS_SEGS] = {
-      0.0f,   /* nsegs ==1 */
-      0.559f, /* 2 */
-      0.642f, /* 3 */
-      0.551f, /* 4 */
-      0.646f, /* 5 */
-      0.624f, /* 6 */
-      0.646f, /* 7 */
-      0.619f, /* 8 */
-      0.647f, /* 9 */
-      0.639f, /* 10 */
-      0.647f, /* 11 */
-  };
+  float fullness;
 
-  n = bv->vmesh->count;
+  n_bndv = bv->vmesh->count;
 
   /* Same bevel as that of 3 edges of vert in a cube */
-  if (n == 3 && tri_corner_test(bp, bv) != -1 && bp->pro_super_r != PRO_SQUARE_IN_R) {
+  if (n_bndv == 3 && tri_corner_test(bp, bv) != -1 && bp->pro_super_r != PRO_SQUARE_IN_R) {
     return tri_corner_adj_vmesh(bp, bv);
   }
 
-  /* First construct an initial control mesh, with nseg==2 */
-  ns = bv->vmesh->seg;
-  vm0 = new_adj_vmesh(mem_arena, n, 2, bv->vmesh->boundstart);
+  /* First construct an initial control mesh, with nseg == 2. */
+  nseg = bv->vmesh->seg;
+  vm0 = new_adj_vmesh(mem_arena, n_bndv, 2, bv->vmesh->boundstart);
 
+  /* Find the center of the boundverts that make up the vmesh. */
   bndv = vm0->boundstart;
-  zero_v3(co);
-  for (i = 0; i < n; i++) {
-    /* Boundaries just divide input polygon edges into 2 even segments */
+  zero_v3(boundverts_center);
+  for (i = 0; i < n_bndv; i++) {
+    /* Boundaries just divide input polygon edges into 2 even segments. */
     copy_v3_v3(mesh_vert(vm0, i, 0, 0)->co, bndv->nv.co);
     get_profile_point(bp, &bndv->profile, 1, 2, mesh_vert(vm0, i, 0, 1)->co);
-    add_v3_v3(co, bndv->nv.co);
+    add_v3_v3(boundverts_center, bndv->nv.co);
     bndv = bndv->next;
   }
-  /* To place center vertex:
-   * coa is original vertex
-   * co is centroid of boundary corners
-   * cob is reflection of coa in across co.
-   * Calculate 'fullness' = fraction of way
-   * from co to coa (if positive) or to cob (if negative).
-   */
-  copy_v3_v3(coa, bv->v->co);
-  mul_v3_fl(co, 1.0f / (float)n);
-  sub_v3_v3v3(cob, co, coa);
-  add_v3_v3(cob, co);
-
-  /* An offline optimization process found fullness that let to closest fit to sphere as
-   * a function of r and ns (for case of cube corner) */
-  r = bp->pro_super_r;
-  p = bp->profile;
-  if (r == PRO_LINE_R) {
-    fullness = 0.0f;
-  }
-  else if (r == PRO_CIRCLE_R && ns > 0 && ns <= CIRCLE_FULLNESS_SEGS) {
-    fullness = circle_fullness[ns - 1];
-  }
-  else {
-    /* linear regression fit found best linear function, separately for even/odd segs */
-    if (ns % 2 == 0) {
-      fullness = 2.4506f * p - 0.00000300f * ns - 0.6266f;
+  mul_v3_fl(boundverts_center, 1.0f / (float)n_bndv);
+
+  /* To place the center vertex:
+   * 'negative_fullest' is the reflection of the original vertex across the boundverts' center.
+   * 'fullness' is the fraction of the way from the boundvert's centroid to to the original vertex
+   * (if positive) or to negative_fullest (if negative). */
+  copy_v3_v3(original_vertex, bv->v->co);
+  sub_v3_v3v3(negative_fullest, boundverts_center, original_vertex);
+  add_v3_v3(negative_fullest, boundverts_center);
+
+  /* Find the vertex mesh's start center with the profile's fullness */
+  fullness = bp->pro_spacing.fullness;
+  sub_v3_v3v3(center_direction, original_vertex, boundverts_center);
+  if (len_squared_v3(center_direction) > BEVEL_EPSILON_SQ) {
+    if (bp->use_custom_profile) {
+      fullness *= 2.0f;
+      madd_v3_v3v3fl(mesh_vert(vm0, 0, 1, 1)->co, negative_fullest, center_direction, fullness);
     }
     else {
-      fullness = 2.3635f * p + 0.000152f * ns - 0.6060f;
+      madd_v3_v3v3fl(mesh_vert(vm0, 0, 1, 1)->co, boundverts_center, center_direction, fullness);
     }
   }
-  sub_v3_v3v3(dir, coa, co);
-  if (len_squared_v3(dir) > BEVEL_EPSILON_SQ) {
-    madd_v3_v3fl(co, dir, fullness);
+  else {
+    copy_v3_v3(mesh_vert(vm0, 0, 1, 1)->co, boundverts_center);
   }
-  copy_v3_v3(mesh_vert(vm0, 0, 1, 1)->co, co);
   vmesh_copy_equiv_verts(vm0);
 
+  /* Do the subdivision process to go from the two segment start mesh to the final vertex mesh. */
   vm1 = vm0;
   do {
     vm1 = cubic_subdiv(bp, vm1);
-  } while (vm1->seg < ns);
-  if (vm1->seg != ns) {
-    vm1 = interp_vmesh(bp, vm1, ns);
+  } while (vm1->seg < nseg);
+  if (vm1->seg != nseg) {
+    vm1 = interp_vmesh(bp, vm1, nseg);
   }
   return vm1;
 }
@@ -4012,15 +4334,16 @@ static void snap_to_pipe_profile(BoundVert *vpipe, bool midline, float co[3])
   Profile *pro = &vpipe->profile;
   EdgeHalf *e = vpipe->ebev;
 
-  copy_v3_v3(va, pro->coa);
-  copy_v3_v3(vb, pro->cob);
+  copy_v3_v3(va, pro->start);
+  copy_v3_v3(vb, pro->end);
 
+  /* Get a plane with the normal pointing along the beveled edge */
   sub_v3_v3v3(edir, e->e->v1->co, e->e->v2->co);
-
   plane_from_point_normal_v3(plane, co, edir);
+
   closest_to_plane_v3(va0, plane, va);
   closest_to_plane_v3(vb0, plane, vb);
-  closest_to_plane_v3(vmid0, plane, pro->midco);
+  closest_to_plane_v3(vmid0, plane, pro->middle);
   if (make_unit_square_map(va0, vmid0, vb0, m)) {
     /* Transform co and project it onto superellipse */
     if (!invert_m4_m4(minv, m)) {
@@ -4030,6 +4353,7 @@ static void snap_to_pipe_profile(BoundVert *vpipe, bool midline, float co[3])
     }
     mul_v3_m4v3(p, minv, co);
     snap_to_superellipsoid(p, pro->super_r, midline);
+
     mul_v3_m4v3(snap, m, p);
     copy_v3_v3(co, snap);
   }
@@ -4040,38 +4364,123 @@ static void snap_to_pipe_profile(BoundVert *vpipe, bool midline, float co[3])
   }
 }
 
-/* See pipe_test for conditions that make 'pipe'; vpipe is the return value from that.
+/** See pipe_test for conditions that make 'pipe'; vpipe is the return value from that.
  * We want to make an ADJ mesh but then snap the vertices to the profile in a plane
- * perpendicular to the pipes.
- * A tricky case is for the 'square' profiles and an even nseg: we want certain vertices
- * to snap to the midline on the pipe, not just to one plane or the other. */
+ * perpendicular to the pipes. */
 static VMesh *pipe_adj_vmesh(BevelParams *bp, BevVert *bv, BoundVert *vpipe)
 {
-  int i, j, k, n, ns, ns2, ipipe1, ipipe2;
+#ifdef DEBUG_CUSTOM_PROFILE_PIPE
+  printf("PIPE ADJ VMESH\n");
+  float green[4] = {0.0f, 1.0f, 0.0f, 1.0f};
+  float blue[4] = {0.0f, 0.0f, 1.0f, 1.0f};
+  float red[4] = {1.0f, 0.0f, 0.0f, 1.0f};
+  float white[4] = {1.0f, 1.0f, 1.0f, 1.0f};
+  float *color;
+#endif
+  int i, j, k, n_bndv, ns, half_ns, ipipe1, ipipe2, ring;
   VMesh *vm;
   bool even, midline;
+  float *profile_point_pipe1, *profile_point_pipe2, f;
 
+  /* Some unecessary overhead running this subdivision with custom profile snapping later on. */
   vm = adj_vmesh(bp, bv);
 
-  /* Now snap all interior coordinates to be on the epipe profile */
-  n = bv->vmesh->count;
+  /* Now snap all interior coordinates to be on the epipe profile. */
+  n_bndv = bv->vmesh->count;
   ns = bv->vmesh->seg;
-  ns2 = ns / 2;
+  half_ns = ns / 2;
   even = (ns % 2) == 0;
   ipipe1 = vpipe->index;
   ipipe2 = vpipe->next->next->index;
-  for (i = 0; i < n; i++) {
-    for (j = 1; j <= ns2; j++) {
-      for (k = 0; k <= ns2; k++) {
+
+#ifdef DEBUG_CUSTOM_PROFILE_PIPE
+  printf("ipipe1: %d\n", ipipe1);
+  printf("ipipe2: %d\n", ipipe2);
+#endif
+
+  for (i = 0; i < n_bndv; i++) {
+    for (j = 1; j <= half_ns; j++) {
+      for (k = 0; k <= half_ns; k++) {
         if (!is_canon(vm, i, j, k)) {
           continue;
         }
-        midline = even && k == ns2 && ((i == 0 && j == ns2) || (i == ipipe1 || i == ipipe2));
-        snap_to_pipe_profile(vpipe, midline, mesh_vert(vm, i, j, k)->co);
+        if (bp->use_custom_profile) {
+          /* Find both profile vertices that correspond to this point. */
+          if (i == ipipe1 || i == ipipe2) {
+            if (n_bndv == 3 && i == ipipe1) {
+              /* This part of the vmesh is the triangular corner between the two pipe profiles. */
+              ring = max_ii(j, k);
+              profile_point_pipe2 = mesh_vert(vm, i, 0, ring)->co;
+              profile_point_pipe1 = mesh_vert(vm, i, ring, 0)->co;
+              /* End profile index increases with k on one side and j on the other. */
+              f = ((k < j) ? min_ff(j, k) : ((2.0f * ring) - j)) / (2.0f * ring);
+            }
+            else {
+              /* This is part of either pipe profile boundvert area in the 4-way intersection. */
+              profile_point_pipe1 = mesh_vert(vm, i, 0, k)->co;
+              profile_point_pipe2 = mesh_vert(vm, (i == ipipe1) ? ipipe2 : ipipe1, 0, ns - k)->co;
+              f = (float)j / (float)ns; /* The ring index brings us closer to the other side. */
+            }
+          }
+          else {
+            /* The profile vertices are on both ends of each of the side profile's rings. */
+            profile_point_pipe1 = mesh_vert(vm, i, j, 0)->co;
+            profile_point_pipe2 = mesh_vert(vm, i, j, ns)->co;
+            f = (float)k / (float)ns; /* Ring runs along the pipe, so segment is used here. */
+          }
+
+          /* Place the vertex by interpolatin between the two profile points using the factor. */
+          interp_v3_v3v3(mesh_vert(vm, i, j, k)->co, profile_point_pipe1, profile_point_pipe2, f);
+#ifdef DEBUG_CUSTOM_PROFILE_PIPE
+          printf("(%d, %d, %d)\n", i, j, k);
+          printf("f: %.3f\n", f);
+          printf("point 1: (%.3f, %.3f, %.3f)\n",
+                 profile_point_pipe1[0],
+                 profile_point_pipe1[1],
+                 profile_point_pipe1[2]);
+          printf("point 2: (%.3f, %.3f, %.3f)\n",
+                 profile_point_pipe2[0],
+                 profile_point_pipe2[1],
+                 profile_point_pipe2[2]);
+#endif
+        }
+        else {
+          /* A tricky case is for the 'square' profiles and an even nseg: we want certain
+           * vertices to snap to the midline on the pipe, not just to one plane or the other. */
+          midline = even && k == half_ns &&
+                    ((i == 0 && j == half_ns) || (i == ipipe1 || i == ipipe2));
+          snap_to_pipe_profile(vpipe, midline, mesh_vert(vm, i, j, k)->co);
+        }
       }
     }
   }
-
+#ifdef DEBUG_CUSTOM_PROFILE_PIPE
+  /* Draw the locations of all the vertices after the "snapping" process */
+  for (i = 0; i < n_bndv; i++) {
+    for (j = 1; j <= half_ns; j++) {
+      for (k = 1; k <= ns; k++) {
+        if (!is_canon(vm, i, j, k)) {
+          continue;
+        }
+        switch (i) {
+          case 0:
+            color = red;
+            break;
+          case 1:
+            color = green;
+            break;
+          case 2:
+            color = blue;
+            break;
+          case 3:
+            color = white;
+            break;
+        }
+        DRW_debug_sphere(mesh_vert(vm, i, j, k)->co, 0.01f, color);
+      }
+    }
+  }
+#endif
   return vm;
 }
 
@@ -4123,7 +4532,7 @@ static float snap_face_dist_squared(float *co, BMFace *f, BMEdge **r_snap_e, flo
   BMEdge *e;
   float closest[3];
 
-  beste_d2 = 1e20;
+  beste_d2 = 1e20f;
   BM_ITER_ELEM (e, &iter, f, BM_EDGES_OF_FACE) {
     closest_to_line_segment_v3(closest, co, e->v1->co, e->v2->co);
     d2 = len_squared_v3v3(closest, co);
@@ -4243,7 +4652,7 @@ static void closer_v3_v3v3v3(float r[3], float a[3], float b[3], float v[3])
  */
 static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv)
 {
-  int n, ns, ns2, odd, i, j, k, ikind, im1, clstride, iprev, akind;
+  int n_bndv, ns, ns2, odd, i, j, k, ikind, im1, clstride, iprev, ang_kind;
   float bndco[3], dir1[3], dir2[3], co1[3], co2[3], meet1[3], meet2[3], v1co[3], v2co[3];
   float *on_edge_cur, *on_edge_prev, *p;
   float ns2inv, finalfrac, ang;
@@ -4253,26 +4662,26 @@ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv)
   float *centerline;
   bool *cset, v1set, v2set;
 
-  n = bv->vmesh->count;
+  n_bndv = bv->vmesh->count;
   ns = bv->vmesh->seg;
   ns2 = ns / 2;
   odd = ns % 2;
   ns2inv = 1.0f / (float)ns2;
-  vm = new_adj_vmesh(bp->mem_arena, n, ns, bv->vmesh->boundstart);
+  vm = new_adj_vmesh(bp->mem_arena, n_bndv, ns, bv->vmesh->boundstart);
   clstride = 3 * (ns2 + 1);
-  centerline = MEM_mallocN(clstride * n * sizeof(float), "bevel");
-  cset = MEM_callocN(n * sizeof(bool), "bevel");
+  centerline = MEM_mallocN((size_t)(clstride * n_bndv) * sizeof(float), "bevel");
+  cset = MEM_callocN((size_t)n_bndv * sizeof(bool), "bevel");
 
   /* find on_edge, place on bndv[i]'s elast where offset line would meet,
    * taking min-distance-to bv->v with position where next sector's offset line would meet */
   bndv = vm->boundstart;
-  for (i = 0; i < n; i++) {
+  for (i = 0; i < n_bndv; i++) {
     copy_v3_v3(bndco, bndv->nv.co);
     e1 = bndv->efirst;
     e2 = bndv->elast;
-    akind = 0;
+    ang_kind = ANGLE_STRAIGHT;
     if (e1 && e2) {
-      akind = edges_angle_kind(e1, e2, bv->v);
+      ang_kind = edges_angle_kind(e1, e2, bv->v);
     }
     if (bndv->is_patch_start) {
       mid_v3_v3v3(centerline + clstride * i, bndv->nv.co, bndv->next->nv.co);
@@ -4288,13 +4697,13 @@ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv)
     else if (bndv->is_arc_start) {
       e1 = bndv->efirst;
       e2 = bndv->next->efirst;
-      copy_v3_v3(centerline + clstride * i, bndv->profile.midco);
+      copy_v3_v3(centerline + clstride * i, bndv->profile.middle);
       bndv = bndv->next;
       cset[i] = true;
       i++;
       /* leave cset[i] where it was - probably false, unless i == n - 1 */
     }
-    else if (akind < 0) {
+    else if (ang_kind == ANGLE_SMALLER) {
       sub_v3_v3v3(dir1, e1->e->v1->co, e1->e->v2->co);
       sub_v3_v3v3(dir2, e2->e->v1->co, e2->e->v2->co);
       add_v3_v3v3(co1, bndco, dir1);
@@ -4321,7 +4730,7 @@ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv)
 
       /* want on_edge[i] to be min dist to bv->v of v2co and the v1co of next iteration */
       on_edge_cur = centerline + clstride * i;
-      iprev = (i == 0) ? n - 1 : i - 1;
+      iprev = (i == 0) ? n_bndv - 1 : i - 1;
       on_edge_prev = centerline + clstride * iprev;
       if (v2set) {
         if (cset[i]) {
@@ -4346,7 +4755,7 @@ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv)
   }
   /* Maybe not everything was set by the previous loop */
   bndv = vm->boundstart;
-  for (i = 0; i < n; i++) {
+  for (i = 0; i < n_bndv; i++) {
     if (!cset[i]) {
       on_edge_cur = centerline + clstride * i;
       e1 = bndv->next->efirst;
@@ -4381,7 +4790,7 @@ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv)
   /* fill in rest of centerlines by interpolation */
   copy_v3_v3(co2, bv->v->co);
   bndv = vm->boundstart;
-  for (i = 0; i < n; i++) {
+  for (i = 0; i < n_bndv; i++) {
     if (odd) {
       ang = 0.5f * angle_v3v3v3(bndv->nv.co, co1, bndv->next->nv.co);
       if (ang > BEVEL_SMALL_ANG) {
@@ -4389,7 +4798,7 @@ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv)
          * such that the base of the triangle is 1.
          * This is used in interpolation along centerline in odd case.
          * To avoid too big a drop from bv, cap finalfrac a 0.8 arbitrarily */
-        finalfrac = 0.5f / sin(ang);
+        finalfrac = 0.5f / sinf(ang);
         if (finalfrac > 0.8f) {
           finalfrac = 0.8f;
         }
@@ -4412,9 +4821,9 @@ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv)
 
   /* coords of edges and mid or near-mid line */
   bndv = vm->boundstart;
-  for (i = 0; i < n; i++) {
+  for (i = 0; i < n_bndv; i++) {
     copy_v3_v3(co1, bndv->nv.co);
-    copy_v3_v3(co2, centerline + clstride * (i == 0 ? n - 1 : i - 1));
+    copy_v3_v3(co2, centerline + clstride * (i == 0 ? n_bndv - 1 : i - 1));
     for (j = 0; j < ns2 + odd; j++) {
       interp_v3_v3v3(mesh_vert(vm, i, j, 0)->co, co1, co2, j * ns2inv);
     }
@@ -4431,8 +4840,8 @@ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv)
 
   /* fill in interior points by interpolation from edges to centerlines */
   bndv = vm->boundstart;
-  for (i = 0; i < n; i++) {
-    im1 = (i == 0) ? n - 1 : i - 1;
+  for (i = 0; i < n_bndv; i++) {
+    im1 = (i == 0) ? n_bndv - 1 : i - 1;
     for (j = 1; j < ns2 + odd; j++) {
       for (k = 1; k <= ns2; k++) {
         ikind = isect_line_line_v3(mesh_vert(vm, i, 0, k)->co,
@@ -4466,43 +4875,43 @@ static VMesh *square_out_adj_vmesh(BevelParams *bp, BevVert *bv)
   return vm;
 }
 
-/*
+/**
  * Given that the boundary is built and the boundary BMVerts have been made,
  * calculate the positions of the interior mesh points for the M_ADJ pattern,
- * using cubic subdivision, then make the BMVerts and the new faces. */
-static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv)
+ * using cubic subdivision, then make the BMVerts and the new faces.
+ */
+static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv, BoundVert *vpipe)
 {
-  int n, ns, ns2, odd, i, j, k, ring;
+  int n_bndv, ns, ns2, odd, i, j, k, ring;
   VMesh *vm1, *vm;
-  BoundVert *v;
+  BoundVert *bndv;
   BMVert *bmv1, *bmv2, *bmv3, *bmv4;
   BMFace *f, *f2, *r_f;
   BMEdge *bme, *bme1, *bme2, *bme3;
   EdgeHalf *e;
-  BoundVert *vpipe;
   int mat_nr = bp->mat_nr;
 
-  n = bv->vmesh->count;
+  n_bndv = bv->vmesh->count;
   ns = bv->vmesh->seg;
   ns2 = ns / 2;
   odd = ns % 2;
-  BLI_assert(n >= 3 && ns > 1);
+  BLI_assert(n_bndv >= 3 && ns > 1);
 
-  /* Add support for profiles in vertex only in-plane bevels */
+  /* Add support for profiles in vertex only in-plane bevels. */
   if (bp->vertex_only) {
-    v = bv->vmesh->boundstart;
+    bndv = bv->vmesh->boundstart;
     do {
-      Profile *pro = &v->profile;
+      Profile *pro = &bndv->profile;
+      copy_v3_v3(pro->middle, bv->v->co);
       pro->super_r = bp->pro_super_r;
-      copy_v3_v3(pro->midco, bv->v->co);
-      calculate_profile(bp, v);
-      v = v->next;
-    } while (v != bv->vmesh->boundstart);
+      bool miter_profile = bp->use_custom_profile && (bndv->is_arc_start || bndv->is_patch_start);
+      /* Orientation doesn't matter when only beveling vertices */
+      calculate_profile(bp, bndv, false, miter_profile);
+      bndv = bndv->next;
+    } while (bndv != bv->vmesh->boundstart);
   }
 
-  vpipe = pipe_test(bv);
-
-  if (bp->pro_super_r == PRO_SQUARE_R && bv->selcount >= 3 && !odd) {
+  if (bp->pro_super_r == PRO_SQUARE_R && bv->selcount >= 3 && !odd && !bp->use_custom_profile) {
     vm1 = square_out_adj_vmesh(bp, bv);
   }
   else if (vpipe) {
@@ -4512,7 +4921,7 @@ static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv)
     vm1 = tri_corner_adj_vmesh(bp, bv);
     /* the PRO_SQUARE_IN_R profile has boundary edges that merge
      * and no internal ring polys except possibly center ngon */
-    if (bp->pro_super_r == PRO_SQUARE_IN_R) {
+    if (bp->pro_super_r == PRO_SQUARE_IN_R && !bp->use_custom_profile) {
       build_square_in_vmesh(bp, bm, bv, vm1);
       return;
     }
@@ -4523,7 +4932,7 @@ static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv)
 
   /* copy final vmesh into bv->vmesh, make BMVerts and BMFaces */
   vm = bv->vmesh;
-  for (i = 0; i < n; i++) {
+  for (i = 0; i < n_bndv; i++) {
     for (j = 0; j <= ns2; j++) {
       for (k = 0; k <= ns; k++) {
         if (j == 0 && (k == 0 || k == ns)) {
@@ -4539,16 +4948,16 @@ static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv)
   }
   vmesh_copy_equiv_verts(vm);
   /* make the polygons */
-  v = vm->boundstart;
+  bndv = vm->boundstart;
   do {
-    i = v->index;
-    f = boundvert_rep_face(v, NULL);
-    f2 = boundvert_rep_face(v->next, NULL);
+    i = bndv->index;
+    f = boundvert_rep_face(bndv, NULL);
+    f2 = boundvert_rep_face(bndv->next, NULL);
     if (bp->vertex_only) {
-      e = v->efirst;
+      e = bndv->efirst;
     }
     else {
-      e = v->ebev;
+      e = bndv->ebev;
     }
     bme = e ? e->e : NULL;
     /* For odd ns, make polys with lower left corner at (i,j,k) for
@@ -4576,7 +4985,7 @@ static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv)
                                        f2,
                                        NULL,
                                        NULL,
-                                       v->next->efirst->e,
+                                       bndv->next->efirst->e,
                                        bme,
                                        mat_nr);
             }
@@ -4617,8 +5026,8 @@ static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv)
           else {
             bme1 = k == ns2 - 1 ? bme : NULL;
             bme3 = NULL;
-            if (j == ns2 - 1 && v->prev->ebev) {
-              bme3 = v->prev->ebev->e;
+            if (j == ns2 - 1 && bndv->prev->ebev) {
+              bme3 = bndv->prev->ebev->e;
             }
             bme2 = bme1 != NULL ? bme1 : bme3;
             r_f = bev_create_quad_ex(
@@ -4628,14 +5037,14 @@ static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv)
         record_face_kind(bp, r_f, F_VERT);
       }
     }
-  } while ((v = v->next) != vm->boundstart);
+  } while ((bndv = bndv->next) != vm->boundstart);
 
   /* Fix UVs along center lines if even number of segments */
   if (!odd) {
-    v = vm->boundstart;
+    bndv = vm->boundstart;
     do {
-      i = v->index;
-      if (!v->any_seam) {
+      i = bndv->index;
+      if (!bndv->any_seam) {
         for (ring = 1; ring < ns2; ring++) {
           BMVert *v_uv = mesh_vert(vm, i, ring, ns2)->v;
           if (v_uv) {
@@ -4643,7 +5052,7 @@ static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv)
           }
         }
       }
-    } while ((v = v->next) != vm->boundstart);
+    } while ((bndv = bndv->next) != vm->boundstart);
     bmv1 = mesh_vert(vm, 0, ns2, ns2)->v;
     if (bp->vertex_only || count_bound_vert_seams(bv) <= 1) {
       bev_merge_uvs(bm, bmv1);
@@ -4656,6 +5065,179 @@ static void bevel_build_rings(BevelParams *bp, BMesh *bm, BevVert *bv)
   }
 }
 
+/** Builds the vertex mesh when the vertex mesh type is set to "cut off" with a face closing
+ * off each incoming edge's profile */
+/* HANS-TODO: Make cutoff VMesh work with outer miter != sharp. This should be possible but there
+ * are two problems currently:
+ *  - Miter profiles don't have plane_no filled, so down direction is incorrect.
+ *  - Indexing profile points of miters with (i, 0, k) seems to return zero except for the first
+ *    and last profile point. */
+/* HANS-TODO: Use repface / edge arrays for UV interpolation properly. */
+static void bevel_build_cutoff(BevelParams *bp, BMesh *bm, BevVert *bv)
+{
+#ifdef DEBUG_CUSTOM_PROFILE_CUTOFF
+  printf("BEVEL BUILD CUTOFF\n");
+  int j;
+#endif
+  int i;
+  int n_bndv = bv->vmesh->count;
+  BoundVert *bndv;
+  float length;
+  float down_direction[3], new_vert[3];
+  bool build_center_face;
+  /* BMFace *repface; */
+  BMVert **face_bmverts = NULL;
+  BMEdge **bmedges = NULL;
+  BMFace **bmfaces = NULL;
+
+  /* Find the locations for the corner vertices at the bottom of the cutoff faces. */
+  bndv = bv->vmesh->boundstart;
+  do {
+    i = bndv->index;
+
+    /* Find the "down" direction for this side of the cutoff face. */
+    /* Find the direction along the intersection of the two adjecent profile normals. */
+    cross_v3_v3v3(down_direction, bndv->profile.plane_no, bndv->prev->profile.plane_no);
+    if (dot_v3v3(down_direction, bv->v->no) > 0.0f) {
+      negate_v3(down_direction);
+    }
+
+    /* Move down from the boundvert by average profile height from the two adjacent profiles. */
+    length = (bndv->profile.height / sqrtf(2.0f) + bndv->prev->profile.height / sqrtf(2.0f)) / 2;
+    madd_v3_v3v3fl(new_vert, bndv->nv.co, down_direction, length);
+
+    /* Use this location for this profile's first corner vert and the last profile's second. */
+    copy_v3_v3(mesh_vert(bv->vmesh, i, 1, 0)->co, new_vert);
+    copy_v3_v3(mesh_vert(bv->vmesh, bndv->prev->index, 1, 1)->co, new_vert);
+
+  } while ((bndv = bndv->next) != bv->vmesh->boundstart);
+
+#ifdef DEBUG_CUSTOM_PROFILE_CUTOFF
+  printf("Corner vertices:\n");
+  for (j = 0; j < n_bndv; j++) {
+    printf("  (%.3f, %.3f, %.3f)\n",
+           (double)mesh_vert(bv->vmesh, j, 1, 0)->co[0],
+           (double)mesh_vert(bv->vmesh, j, 1, 0)->co[1],
+           (double)mesh_vert(bv->vmesh, j, 1, 0)->co[2]);
+  }
+#endif
+
+  /* Disable the center face if the corner vertices share the same location. */
+  build_center_face = true;
+  if (n_bndv == 3) { /* Vertices only collapse with a 3-way VMesh. */
+    build_center_face &= len_squared_v3v3(mesh_vert(bv->vmesh, 0, 1, 0)->co,
+                                          mesh_vert(bv->vmesh, 1, 1, 0)->co) > BEVEL_EPSILON;
+    build_center_face &= len_squared_v3v3(mesh_vert(bv->vmesh, 0, 1, 0)->co,
+                                          mesh_vert(bv->vmesh, 2, 1, 0)->co) > BEVEL_EPSILON;
+    build_center_face &= len_squared_v3v3(mesh_vert(bv->vmesh, 1, 1, 0)->co,
+                                          mesh_vert(bv->vmesh, 2, 1, 0)->co) > BEVEL_EPSILON;
+  }
+#ifdef DEBUG_CUSTOM_PROFILE_CUTOFF
+  printf("build_center_face: %d\n", build_center_face);
+#endif
+
+  /* Create the corner vertex BMVerts. */
+  if (build_center_face) {
+    do {
+      i = bndv->index;
+      create_mesh_bmvert(bm, bv->vmesh, i, 1, 0, bv->v);
+      /* The second corner vertex for the previous profile shares this BMVert. */
+      mesh_vert(bv->vmesh, bndv->prev->index, 1, 1)->v = mesh_vert(bv->vmesh, i, 1, 0)->v;
+
+    } while ((bndv = bndv->next) != bv->vmesh->boundstart);
+  }
+  else {
+    /* Use the same BMVert for all of the corner vertices. */
+    create_mesh_bmvert(bm, bv->vmesh, 0, 1, 0, bv->v);
+    for (i = 1; i < n_bndv; i++) {
+      mesh_vert(bv->vmesh, i, 1, 0)->v = mesh_vert(bv->vmesh, 0, 1, 0)->v;
+    }
+  }
+
+  /* Build the profile cutoff faces. */
+  /* Extra one or two for corner vertices and one for last point along profile, or the size of the
+   * center face array if it's bigger. */
+#ifdef DEBUG_CUSTOM_PROFILE_CUTOFF
+  printf("Building profile cutoff faces.\n");
+#endif
+  face_bmverts = BLI_memarena_alloc(
+      bp->mem_arena, ((size_t)max_ii(bp->seg + 2 + build_center_face, n_bndv) * sizeof(BMVert *)));
+  bndv = bv->vmesh->boundstart;
+  do {
+    i = bndv->index;
+    BLI_array_staticdeclare(bmedges, BM_DEFAULT_NGON_STACK_SIZE);
+    BLI_array_staticdeclare(bmfaces, BM_DEFAULT_NGON_STACK_SIZE);
+
+    /* Add the first corner vertex under this boundvert */
+    face_bmverts[0] = mesh_vert(bv->vmesh, i, 1, 0)->v;
+
+#ifdef DEBUG_CUSTOM_PROFILE_CUTOFF
+    printf("Profile Number %d:\n", i);
+    if (bndv->is_patch_start || bndv->is_arc_start) {
+      printf("  Miter profile\n");
+    }
+    printf("  Corner 1: (%0.3f, %0.3f, %0.3f)\n",
+           (double)mesh_vert(bv->vmesh, i, 1, 0)->co[0],
+           (double)mesh_vert(bv->vmesh, i, 1, 0)->co[1],
+           (double)mesh_vert(bv->vmesh, i, 1, 0)->co[2]);
+#endif
+
+    /* Add profile point vertices to the face, including the last one. */
+    for (int k = 0; k < bp->seg + 1; k++) {
+      face_bmverts[k + 1] = mesh_vert(bv->vmesh, i, 0, k)->v; /* Leave room for first vert. */
+#ifdef DEBUG_CUSTOM_PROFILE_CUTOFF
+      printf("  Profile %d: (%0.3f, %0.3f, %0.3f)\n",
+             k,
+             (double)mesh_vert(bv->vmesh, i, 0, k)->co[0],
+             (double)mesh_vert(bv->vmesh, i, 0, k)->co[1],
+             (double)mesh_vert(bv->vmesh, i, 0, k)->co[2]);
+#endif
+    }
+
+    /* Add the second corner vert to complete the bottom of the face. */
+    if (build_center_face) {
+      face_bmverts[bp->seg + 2] = mesh_vert(bv->vmesh, i, 1, 1)->v;
+#ifdef DEBUG_CUSTOM_PROFILE_CUTOFF
+      printf("  Corner 2: (%0.3f, %0.3f, %0.3f)\n",
+             (double)mesh_vert(bv->vmesh, i, 1, 1)->co[0],
+             (double)mesh_vert(bv->vmesh, i, 1, 1)->co[1],
+             (double)mesh_vert(bv->vmesh, i, 1, 1)->co[2]);
+#endif
+    }
+
+    /* Create the profile cutoff face for this boundvert. */
+    /* repface = boundvert_rep_face(bndv, NULL); */
+    bev_create_ngon(bm,
+                    face_bmverts,
+                    bp->seg + 2 + build_center_face,
+                    bmfaces,
+                    NULL,
+                    bmedges,
+                    bp->mat_nr,
+                    true);
+
+    BLI_array_free(bmedges);
+    BLI_array_free(bmfaces);
+  } while ((bndv = bndv->next) != bv->vmesh->boundstart);
+
+  /* Create the bottom face if it should be built, reusing previous face_bmverts allocation. */
+  if (build_center_face) {
+    BLI_array_staticdeclare(bmedges, BM_DEFAULT_NGON_STACK_SIZE);
+    BLI_array_staticdeclare(bmfaces, BM_DEFAULT_NGON_STACK_SIZE);
+
+    /* Add all of the corner vertices to this face. */
+    for (i = 0; i < n_bndv; i++) {
+      /* Add verts from each cutoff face. */
+      face_bmverts[i] = mesh_vert(bv->vmesh, i, 1, 0)->v;
+    }
+    /* BLI_array_append(bmfaces, repface); */
+    bev_create_ngon(bm, face_bmverts, n_bndv, bmfaces, NULL, bmedges, bp->mat_nr, true);
+
+    BLI_array_free(bmedges);
+    BLI_array_free(bmfaces);
+  }
+}
+
 /* If we make a poly out of verts around bv, snapping to rep frep, will uv poly have zero area?
  * The uv poly is made by snapping all outside-of-frep vertices to the closest edge in frep.
  * Assume that this function is called when the only inside-of-frep vertex is vm->boundstart.
@@ -4690,71 +5272,72 @@ static bool is_bad_uv_poly(BevVert *bv, BMFace *frep)
 
 static BMFace *bevel_build_poly(BevelParams *bp, BMesh *bm, BevVert *bv)
 {
-  BMFace *f, *frep, *frep2;
+  BMFace *f, *repface, *frep2;
   int n, k;
   VMesh *vm = bv->vmesh;
-  BoundVert *v;
-  BMEdge *frep_e1, *frep_e2, *frep_e;
-  BMVert **vv = NULL;
-  BMFace **vf = NULL;
-  BMEdge **ve = NULL;
-  BLI_array_staticdeclare(vv, BM_DEFAULT_NGON_STACK_SIZE);
-  BLI_array_staticdeclare(vf, BM_DEFAULT_NGON_STACK_SIZE);
-  BLI_array_staticdeclare(ve, BM_DEFAULT_NGON_STACK_SIZE);
+  BoundVert *bndv;
+  BMEdge *repface_e1, *repface_e2, *frep_e;
+  BMVert **bmverts = NULL;
+  BMEdge **bmedges = NULL;
+  BMFace **bmfaces = NULL;
+  BLI_array_staticdeclare(bmverts, BM_DEFAULT_NGON_STACK_SIZE);
+  BLI_array_staticdeclare(bmedges, BM_DEFAULT_NGON_STACK_SIZE);
+  BLI_array_staticdeclare(bmfaces, BM_DEFAULT_NGON_STACK_SIZE);
 
   if (bv->any_seam) {
-    frep = boundvert_rep_face(vm->boundstart, &frep2);
-    if (frep2 && frep && is_bad_uv_poly(bv, frep)) {
-      frep = frep2;
+    repface = boundvert_rep_face(vm->boundstart, &frep2);
+    if (frep2 && repface && is_bad_uv_poly(bv, repface)) {
+      repface = frep2;
     }
-    get_incident_edges(frep, bv->v, &frep_e1, &frep_e2);
+    get_incident_edges(repface, bv->v, &repface_e1, &repface_e2);
   }
   else {
-    frep = NULL;
-    frep_e1 = frep_e2 = NULL;
+    repface = NULL;
+    repface_e1 = repface_e2 = NULL;
   }
-  v = vm->boundstart;
+  bndv = vm->boundstart;
   n = 0;
   do {
     /* accumulate vertices for vertex ngon */
     /* also accumulate faces in which uv interpolation is to happen for each */
-    BLI_array_append(vv, v->nv.v);
-    if (frep) {
-      BLI_array_append(vf, frep);
-      frep_e = find_closer_edge(v->nv.v->co, frep_e1, frep_e2);
-      BLI_array_append(ve, n > 0 ? frep_e : NULL);
+    BLI_array_append(bmverts, bndv->nv.v);
+    if (repface) {
+      BLI_array_append(bmfaces, repface);
+      frep_e = find_closer_edge(bndv->nv.v->co, repface_e1, repface_e2);
+      BLI_array_append(bmedges, n > 0 ? frep_e : NULL);
     }
     else {
-      BLI_array_append(vf, boundvert_rep_face(v, NULL));
-      BLI_array_append(ve, NULL);
+      BLI_array_append(bmfaces, boundvert_rep_face(bndv, NULL));
+      BLI_array_append(bmedges, NULL);
     }
     n++;
-    if (v->ebev && v->ebev->seg > 1) {
-      for (k = 1; k < v->ebev->seg; k++) {
-        BLI_array_append(vv, mesh_vert(vm, v->index, 0, k)->v);
-        if (frep) {
-          BLI_array_append(vf, frep);
-          frep_e = find_closer_edge(mesh_vert(vm, v->index, 0, k)->v->co, frep_e1, frep_e2);
-          BLI_array_append(ve, k < v->ebev->seg / 2 ? NULL : frep_e);
+    if (bndv->ebev && bndv->ebev->seg > 1) {
+      for (k = 1; k < bndv->ebev->seg; k++) {
+        BLI_array_append(bmverts, mesh_vert(vm, bndv->index, 0, k)->v);
+        if (repface) {
+          BLI_array_append(bmfaces, repface);
+          frep_e = find_closer_edge(
+              mesh_vert(vm, bndv->index, 0, k)->v->co, repface_e1, repface_e2);
+          BLI_array_append(bmedges, k < bndv->ebev->seg / 2 ? NULL : frep_e);
         }
         else {
-          BLI_array_append(vf, boundvert_rep_face(v, NULL));
-          BLI_array_append(ve, NULL);
+          BLI_array_append(bmfaces, boundvert_rep_face(bndv, NULL));
+          BLI_array_append(bmedges, NULL);
         }
         n++;
       }
     }
-  } while ((v = v->next) != vm->boundstart);
+  } while ((bndv = bndv->next) != vm->boundstart);
   if (n > 2) {
-    f = bev_create_ngon(bm, vv, n, vf, frep, ve, bp->mat_nr, true);
+    f = bev_create_ngon(bm, bmverts, n, bmfaces, repface, bmedges, bp->mat_nr, true);
     record_face_kind(bp, f, F_VERT);
   }
   else {
     f = NULL;
   }
-  BLI_array_free(vv);
-  BLI_array_free(vf);
-  BLI_array_free(ve);
+  BLI_array_free(bmverts);
+  BLI_array_free(bmedges);
+  BLI_array_free(bmfaces);
   return f;
 }
 
@@ -4817,6 +5400,7 @@ static void bevel_build_trifan(BevelParams *bp, BMesh *bm, BevVert *bv)
  * we have to make it here. */
 static void bevel_vert_two_edges(BevelParams *bp, BMesh *bm, BevVert *bv)
 {
+
   VMesh *vm = bv->vmesh;
   BMVert *v1, *v2;
   BMEdge *e_eg, *bme;
@@ -4832,18 +5416,20 @@ static void bevel_vert_two_edges(BevelParams *bp, BMesh *bm, BevVert *bv)
 
   ns = vm->seg;
   if (ns > 1) {
-    /* Set up profile parameters */
+    /* Set up profile parameters. */
     bndv = vm->boundstart;
     pro = &bndv->profile;
     pro->super_r = bp->pro_super_r;
-    copy_v3_v3(pro->coa, v1->co);
-    copy_v3_v3(pro->cob, v2->co);
-    copy_v3_v3(pro->midco, bv->v->co);
-    /* don't use projection */
+    copy_v3_v3(pro->start, v1->co);
+    copy_v3_v3(pro->end, v2->co);
+    copy_v3_v3(pro->middle, bv->v->co);
+    /* Don't use projection. */
     zero_v3(pro->plane_co);
     zero_v3(pro->plane_no);
     zero_v3(pro->proj_dir);
-    calculate_profile(bp, bndv);
+    /* there's no orientation chain to continue so the orientation of the bevel doesn't matter. */
+    calculate_profile(bp, bndv, false, false);
+
     for (k = 1; k < ns; k++) {
       get_profile_point(bp, pro, k, ns, co);
       copy_v3_v3(mesh_vert(vm, 0, 0, k)->co, co);
@@ -4874,84 +5460,129 @@ static void bevel_vert_two_edges(BevelParams *bp, BMesh *bm, BevVert *bv)
  * for the boundary and the interior of the vertex mesh. */
 static void build_vmesh(BevelParams *bp, BMesh *bm, BevVert *bv)
 {
-  MemArena *mem_arena = bp->mem_arena;
   VMesh *vm = bv->vmesh;
-  BoundVert *v, *weld1, *weld2;
+  BoundVert *bndv, *weld1, *weld2, *vpipe;
   int n, ns, ns2, i, k, weld;
-  float *va, *vb, co[3];
+  float *v_weld1, *v_weld2, co[3];
 
   n = vm->count;
   ns = vm->seg;
   ns2 = ns / 2;
 
-  vm->mesh = (NewVert *)BLI_memarena_alloc(mem_arena, n * (ns2 + 1) * (ns + 1) * sizeof(NewVert));
+  vm->mesh = (NewVert *)BLI_memarena_alloc(bp->mem_arena,
+                                           (size_t)(n * (ns2 + 1) * (ns + 1)) * sizeof(NewVert));
 
-  /* special case: two beveled ends welded together */
+  /* Special case: just two beveled edges welded together. */
   weld = (bv->selcount == 2) && (vm->count == 2);
-  weld1 = weld2 = NULL; /* will hold two BoundVerts involved in weld */
+  weld1 = weld2 = NULL; /* Will hold two BoundVerts involved in weld. */
 
-  /* make (i, 0, 0) mesh verts for all i */
-  v = vm->boundstart;
+  /* Make (i, 0, 0) mesh verts for all i boundverts. */
+  bndv = vm->boundstart;
   do {
-    i = v->index;
-    copy_v3_v3(mesh_vert(vm, i, 0, 0)->co, v->nv.co);
-    create_mesh_bmvert(bm, vm, i, 0, 0, bv->v);
-    v->nv.v = mesh_vert(vm, i, 0, 0)->v;
-    if (weld && v->ebev) {
+    i = bndv->index;
+    copy_v3_v3(mesh_vert(vm, i, 0, 0)->co, bndv->nv.co); /* Mesh NewVert to boundary NewVert. */
+    create_mesh_bmvert(bm, vm, i, 0, 0, bv->v);          /* Create BMVert for that NewVert. */
+    bndv->nv.v = mesh_vert(vm, i, 0, 0)->v; /* Use the BMVert for the BoundVert's NewVert. */
+
+    /* Find boundverts and move profile planes if this is a weld case. */
+    if (weld && bndv->ebev) {
       if (!weld1) {
-        weld1 = v;
+        weld1 = bndv;
       }
-      else {
-        weld2 = v;
-        move_weld_profile_planes(bv, weld1, weld2);
-        calculate_profile(bp, weld1);
-        calculate_profile(bp, weld2);
+      else { /* Get the last of the two BoundVerts. */
+        weld2 = bndv;
+        move_weld_profile_planes(bv, weld1, weld2); /* Profile recalculated in next loop. */
       }
     }
-  } while ((v = v->next) != vm->boundstart);
+  } while ((bndv = bndv->next) != vm->boundstart);
 
-  /* copy other ends to (i, 0, ns) for all i, and fill in profiles for edges */
-  v = vm->boundstart;
+  /* Create new vertices and place them based on the profiles. */
+  /* Copy other ends to (i, 0, ns) for all i, and fill in profiles for edges. */
+  bndv = vm->boundstart;
   do {
-    i = v->index;
-    copy_mesh_vert(vm, i, 0, ns, v->next->index, 0, 0);
-    for (k = 1; k < ns; k++) {
-      if (v->ebev && vm->mesh_kind != M_ADJ) {
-        get_profile_point(bp, &v->profile, k, ns, co);
-        copy_v3_v3(mesh_vert(vm, i, 0, k)->co, co);
-        if (!weld) {
-          create_mesh_bmvert(bm, vm, i, 0, k, bv->v);
+    i = bndv->index;
+    /* bndv's last vert along the boundary arc is the the first of the next BoundVert's arc. */
+    copy_mesh_vert(vm, i, 0, ns, bndv->next->index, 0, 0);
+
+    /* Fix the profile orientations if it's not a miter profile. */
+    if (bp->use_custom_profile && !bndv->is_arc_start && !bndv->is_patch_start) {
+      calculate_profile(bp, bndv, !bndv->is_profile_start, false);
+    }
+    if (vm->mesh_kind != M_ADJ) {
+      for (k = 1; k < ns; k++) {
+        if (bndv->ebev) {
+          get_profile_point(bp, &bndv->profile, k, ns, co);
+          copy_v3_v3(mesh_vert(vm, i, 0, k)->co, co); /* Get NewVert location from profile coord */
+          if (!weld) {
+            /* This is done later with (possibly) better positions for the weld case. */
+            create_mesh_bmvert(bm, vm, i, 0, k, bv->v);
+          }
+        }
+        else if (n == 2 && !bndv->ebev) {
+          /* case of one edge beveled and this is the v without ebev */
+          /* want to copy the verts from other v, in reverse order */
+          copy_mesh_vert(bv->vmesh, i, 0, k, 1 - i, 0, ns - k);
         }
-      }
-      else if (n == 2 && !v->ebev && vm->mesh_kind != M_ADJ) {
-        /* case of one edge beveled and this is the v without ebev */
-        /* want to copy the verts from other v, in reverse order */
-        copy_mesh_vert(vm, i, 0, k, 1 - i, 0, ns - k);
       }
     }
-  } while ((v = v->next) != vm->boundstart);
+  } while ((bndv = bndv->next) != vm->boundstart);
 
+  /* Build the profile for the weld case (just a connection between the two boundverts). */
   if (weld) {
-    vm->mesh_kind = M_NONE;
+    bv->vmesh->mesh_kind = M_NONE;
     for (k = 1; k < ns; k++) {
-      va = mesh_vert(vm, weld1->index, 0, k)->co;
-      vb = mesh_vert(vm, weld2->index, 0, ns - k)->co;
-      /* if one of the profiles is on a flat plane,
-       * just use the boundary point of the other */
-      if (weld1->profile.super_r == PRO_LINE_R && weld2->profile.super_r != PRO_LINE_R) {
-        copy_v3_v3(co, vb);
-      }
-      else if (weld2->profile.super_r == PRO_LINE_R && weld1->profile.super_r != PRO_LINE_R) {
-        copy_v3_v3(co, va);
+      v_weld1 = mesh_vert(bv->vmesh, weld1->index, 0, k)->co;
+      v_weld2 = mesh_vert(bv->vmesh, weld2->index, 0, ns - k)->co;
+      if (bp->use_custom_profile) {
+        /* Don't bother with special case profile check from below. */
+        mid_v3_v3v3(co, v_weld1, v_weld2);
       }
       else {
-        mid_v3_v3v3(co, va, vb);
+        /* Use the point from the other profile if one is in a special case. */
+        if (weld1->profile.super_r == PRO_LINE_R && weld2->profile.super_r != PRO_LINE_R) {
+          copy_v3_v3(co, v_weld2);
+        }
+        else if (weld2->profile.super_r == PRO_LINE_R && weld1->profile.super_r != PRO_LINE_R) {
+          copy_v3_v3(co, v_weld1);
+        }
+        else {
+          /* In case the profiles aren't snapped to the same plane, use their midpoint. */
+          mid_v3_v3v3(co, v_weld1, v_weld2);
+        }
       }
-      copy_v3_v3(mesh_vert(vm, weld1->index, 0, k)->co, co);
-      create_mesh_bmvert(bm, vm, weld1->index, 0, k, bv->v);
+      copy_v3_v3(mesh_vert(bv->vmesh, weld1->index, 0, k)->co, co);
+      create_mesh_bmvert(bm, bv->vmesh, weld1->index, 0, k, bv->v);
     }
     for (k = 1; k < ns; k++) {
-      copy_mesh_vert(vm, weld2->index, 0, ns - k, weld1->index, 0, k);
+      copy_mesh_vert(bv->vmesh, weld2->index, 0, ns - k, weld1->index, 0, k);
+    }
+  }
+#ifdef DEBUG_CUSTOM_PROFILE_WELD
+  if (weld && ns > 1) {
+    printf("Weld1 profile coordinates:\n");
+    for (k = 0; k < ns; k++) {
+      printf("%0.4f, %0.4f, %0.4f\n",
+             (double)weld1->profile.prof_co[3 * k],
+             (double)weld1->profile.prof_co[3 * k + 1],
+             (double)weld1->profile.prof_co[3 * k + 2]);
+    }
+    printf("Weld2 profile coordinates\n");
+    for (k = 0; k < ns; k++) {
+      printf("%0.4f, %0.4f, %0.4f\n",
+             (double)weld2->profile.prof_co[3 * k],
+             (double)weld2->profile.prof_co[3 * k + 1],
+             (double)weld2->profile.prof_co[3 * k + 2]);
+    }
+  }
+#endif
+
+  /* Make sure the pipe case ADJ mesh is used for both the "Grid Fill" (ADJ) and cutoff options. */
+  vpipe = NULL;
+  if ((vm->count == 3 || vm->count == 4) && bp->seg > 1) {
+    /* Result is passed to bevel_build_rings to avoid overhead. */
+    vpipe = pipe_test(bv);
+    if (vpipe) {
+      vm->mesh_kind = M_ADJ;
     }
   }
 
@@ -4965,11 +5596,13 @@ static void build_vmesh(BevelParams *bp, BMesh *bm, BevVert *bv)
       bevel_build_poly(bp, bm, bv);
       break;
     case M_ADJ:
-      bevel_build_rings(bp, bm, bv);
+      bevel_build_rings(bp, bm, bv, vpipe);
       break;
     case M_TRI_FAN:
       bevel_build_trifan(bp, bm, bv);
       break;
+    case M_CUTOFF:
+      bevel_build_cutoff(bp, bm, bv);
   }
 }
 
@@ -5229,9 +5862,7 @@ static void find_bevel_edge_order(BMesh *bm, BevVert *bv, BMEdge *first_bme)
   }
 }
 
-/*
- * Construction around the vertex
- */
+/* Construction around the vertex */
 static BevVert *bevel_vert_construct(BMesh *bm, BevelParams *bp, BMVert *v)
 {
   BMEdge *bme;
@@ -5504,7 +6135,7 @@ static BevVert *bevel_vert_construct(BMesh *bm, BevelParams *bp, BMVert *v)
   return bv;
 }
 
-/* Face f has at least one beveled vertex.  Rebuild f */
+/* Face f has at least one beveled vertex. Rebuild f */
 static bool bev_rebuild_polygon(BMesh *bm, BevelParams *bp, BMFace *f)
 {
   BMIter liter, eiter, fiter;
@@ -5849,7 +6480,7 @@ static void weld_cross_attrs_copy(BMesh *bm, BevVert *bv, VMesh *vm, int vmindex
   }
   BLI_assert(bme_prev && bme_next);
 
-  /* want seams and sharp edges to cross only if that way on both sides */
+  /* Want seams and sharp edges to cross only if that way on both sides. */
   disable_seam = BM_elem_flag_test(bme_prev, BM_ELEM_SEAM) !=
                  BM_elem_flag_test(bme_next, BM_ELEM_SEAM);
   enable_smooth = BM_elem_flag_test(bme_prev, BM_ELEM_SMOOTH) !=
@@ -5869,8 +6500,8 @@ static void weld_cross_attrs_copy(BMesh *bm, BevVert *bv, VMesh *vm, int vmindex
   }
 }
 
-/*
- * Build the polygons along the selected Edge
+/**
+ * Build the bevel polygons along the selected Edge.
  */
 static void bevel_build_edge_polygons(BMesh *bm, BevelParams *bp, BMEdge *bme)
 {
@@ -6269,50 +6900,131 @@ static void find_even_superellipse_chords(int n, float r, double *xvals, double
   find_even_superellipse_chords_general(n, r, xvals, yvals);
 }
 
-/* The superellipse used for multisegment profiles does not
- * have a closed-form way to generate evenly spaced points
- * along an arc. We use an expensive search procedure to find
- * the parameter values that lead to bp->seg even chords.
- * We also want spacing for a number of segments that is
- * a power of 2 >= bp->seg (but at least 4).
- * Use doubles because otherwise we cannot come close to float
- * precision for final results. */
-static void set_profile_spacing(BevelParams *bp)
+/** Find the profile's "fullness," which is the fraction of the space it takes up way from the
+ * boundvert's centroid to to the original vertex for a non-custom profile, or in the case of a
+ * custom profile, the average "height" of the profile points along its centerline. */
+static float find_profile_fullness(BevelParams *bp)
+{
+  float fullness;
+  int nseg = bp->seg;
+
+  /* Precalculated fullness for circle profile radius and more common low seg values. */
+#define CIRCLE_FULLNESS_SEGS 11
+  static const float circle_fullness[CIRCLE_FULLNESS_SEGS] = {
+      0.0f,   /* nsegs == 1 */
+      0.559f, /* 2 */
+      0.642f, /* 3 */
+      0.551f, /* 4 */
+      0.646f, /* 5 */
+      0.624f, /* 6 */
+      0.646f, /* 7 */
+      0.619f, /* 8 */
+      0.647f, /* 9 */
+      0.639f, /* 10 */
+      0.647f, /* 11 */
+  };
+
+  if (bp->use_custom_profile) {
+    /* Set fullness to the average "height" of the profile's sampled points. */
+    fullness = 0.0f;
+    for (int i = 0; i < nseg; i++) { /* Don't use the end points. */
+      fullness += (float)(bp->pro_spacing.xvals[i] + bp->pro_spacing.yvals[i]) / (2.0f * nseg);
+    }
+  }
+  else {
+    /* An offline optimization process found fullness that led to closest fit to sphere as
+     * a function of r and ns (for case of cube corner). */
+    if (bp->pro_super_r == PRO_LINE_R) {
+      fullness = 0.0f;
+    }
+    else if (bp->pro_super_r == PRO_CIRCLE_R && nseg > 0 && nseg <= CIRCLE_FULLNESS_SEGS) {
+      fullness = circle_fullness[nseg - 1];
+    }
+    else {
+      /* Linear regression fit found best linear function, separately for even/odd segs. */
+      if (nseg % 2 == 0) {
+        fullness = 2.4506f * bp->profile - 0.00000300f * nseg - 0.6266f;
+      }
+      else {
+        fullness = 2.3635f * bp->profile + 0.000152f * nseg - 0.6060f;
+      }
+    }
+  }
+  return fullness;
+}
+
+/** Fills the ProfileSpacing struct with the 2D coordinates for the profile's vertices.
+ * The superellipse used for multisegment profiles does not have a closed-form way
+ * to generate evenly spaced points along an arc. We use an expensive search procedure
+ * to find the parameter values that lead to bp->seg even chords.
+ * We also want spacing for a number of segments that is a power of 2 >= bp->seg (but at least 4).
+ * Use doubles because otherwise we cannot come close to float precision for final results.
+ * \param pro_spacing: The struct to fill. Changes depending on whether there needs
+          to be a separate miter profile. */
+static void set_profile_spacing(BevelParams *bp, ProfileSpacing *pro_spacing, bool custom)
 {
   int seg, seg_2;
 
   seg = bp->seg;
+  seg_2 = power_of_2_max_i(bp->seg);
   if (seg > 1) {
-    bp->pro_spacing.xvals = (double *)BLI_memarena_alloc(bp->mem_arena,
-                                                         (seg + 1) * sizeof(double));
-    bp->pro_spacing.yvals = (double *)BLI_memarena_alloc(bp->mem_arena,
-                                                         (seg + 1) * sizeof(double));
-    find_even_superellipse_chords(
-        seg, bp->pro_super_r, bp->pro_spacing.xvals, bp->pro_spacing.yvals);
-    seg_2 = power_of_2_max_i(bp->seg);
+    /* Sample the input number of segments. */
+    pro_spacing->xvals = (double *)BLI_memarena_alloc(bp->mem_arena,
+                                                      (size_t)(seg + 1) * sizeof(double));
+    pro_spacing->yvals = (double *)BLI_memarena_alloc(bp->mem_arena,
+                                                      (size_t)(seg + 1) * sizeof(double));
+    if (custom) {
+      /* Make sure the curve profile's sample table is full. */
+      if (bp->custom_profile->segments_len != seg || !bp->custom_profile->segments) {
+        BKE_curveprofile_initialize((CurveProfile *)bp->custom_profile, (short)seg);
+      }
+
+      /* Copy segment locations into the profile spacing struct. */
+      for (int i = 0; i < seg + 1; i++) {
+        pro_spacing->xvals[i] = (double)bp->custom_profile->segments[i].y;
+        pro_spacing->yvals[i] = (double)bp->custom_profile->segments[i].x;
+      }
+    }
+    else {
+      find_even_superellipse_chords(seg, bp->pro_super_r, pro_spacing->xvals, pro_spacing->yvals);
+    }
+
+    /* Sample the seg_2 segments used for subdividing the vertex meshes. */
     if (seg_2 == 2) {
       seg_2 = 4;
     }
     bp->pro_spacing.seg_2 = seg_2;
     if (seg_2 == seg) {
-      bp->pro_spacing.xvals_2 = bp->pro_spacing.xvals;
-      bp->pro_spacing.yvals_2 = bp->pro_spacing.yvals;
+      pro_spacing->xvals_2 = pro_spacing->xvals;
+      pro_spacing->yvals_2 = pro_spacing->yvals;
     }
     else {
-      bp->pro_spacing.xvals_2 = (double *)BLI_memarena_alloc(bp->mem_arena,
-                                                             (seg_2 + 1) * sizeof(double));
-      bp->pro_spacing.yvals_2 = (double *)BLI_memarena_alloc(bp->mem_arena,
-                                                             (seg_2 + 1) * sizeof(double));
-      find_even_superellipse_chords(
-          seg_2, bp->pro_super_r, bp->pro_spacing.xvals_2, bp->pro_spacing.yvals_2);
+      pro_spacing->xvals_2 = (double *)BLI_memarena_alloc(bp->mem_arena,
+                                                          (size_t)(seg_2 + 1) * sizeof(double));
+      pro_spacing->yvals_2 = (double *)BLI_memarena_alloc(bp->mem_arena,
+                                                          (size_t)(seg_2 + 1) * sizeof(double));
+      if (custom) {
+        /* Make sure the curve profile widget's sample table is full of the seg_2 samples. */
+        BKE_curveprofile_initialize((CurveProfile *)bp->custom_profile, (short)seg_2);
+
+        /* Copy segment locations into the profile spacing struct. */
+        for (int i = 0; i < seg_2 + 1; i++) {
+          pro_spacing->xvals_2[i] = (double)bp->custom_profile->segments[i].y;
+          pro_spacing->yvals_2[i] = (double)bp->custom_profile->segments[i].x;
+        }
+      }
+      else {
+        find_even_superellipse_chords(
+            seg_2, bp->pro_super_r, pro_spacing->xvals_2, pro_spacing->yvals_2);
+      }
     }
   }
-  else {
-    bp->pro_spacing.xvals = NULL;
-    bp->pro_spacing.yvals = NULL;
-    bp->pro_spacing.xvals_2 = NULL;
-    bp->pro_spacing.yvals_2 = NULL;
-    bp->pro_spacing.seg_2 = 0;
+  else { /* Only 1 segment, we don't need any profile information */
+    pro_spacing->xvals = NULL;
+    pro_spacing->yvals = NULL;
+    pro_spacing->xvals_2 = NULL;
+    pro_spacing->yvals_2 = NULL;
+    pro_spacing->seg_2 = 0;
   }
 }
 
@@ -6320,7 +7032,7 @@ static void set_profile_spacing(BevelParams *bp)
  * Assume we have a situation like:
  *
  * a                 d
- * \               /
+ *  \               /
  * A \             / C
  *    \ th1    th2/
  *     b---------c
@@ -6566,7 +7278,10 @@ void BM_mesh_bevel(BMesh *bm,
                    const int miter_outer,
                    const int miter_inner,
                    const float spread,
-                   const float smoothresh)
+                   const float smoothresh,
+                   const bool use_custom_profile,
+                   const struct CurveProfile *custom_profile,
+                   const int vmesh_method)
 {
   BMIter iter, liter;
   BMVert *v, *v_next;
@@ -6578,9 +7293,9 @@ void BM_mesh_bevel(BMesh *bm,
 
   bp.offset = offset;
   bp.offset_type = offset_type;
-  bp.seg = segments;
+  bp.seg = (int)segments;
   bp.profile = profile;
-  bp.pro_super_r = -log(2.0) / log(sqrt(profile)); /* convert to superellipse exponent */
+  bp.pro_super_r = -logf(2.0) / logf(sqrtf(profile)); /* convert to superellipse exponent */
   bp.vertex_only = vertex_only;
   bp.use_weights = use_weights;
   bp.loop_slide = loop_slide;
@@ -6598,6 +7313,15 @@ void BM_mesh_bevel(BMesh *bm,
   bp.spread = spread;
   bp.smoothresh = smoothresh;
   bp.face_hash = NULL;
+  bp.use_custom_profile = use_custom_profile;
+  bp.custom_profile = custom_profile;
+  bp.vmesh_method = vmesh_method;
+
+  /* Disable the miters with the cutoff vertex mesh method, this combination isn't useful anyway */
+  if (bp.vmesh_method == BEVEL_VMESH_CUTOFF) {
+    bp.miter_outer = BEVEL_MITER_SHARP;
+    bp.miter_inner = BEVEL_MITER_SHARP;
+  }
 
   if (profile >= 0.950f) { /* r ~ 692, so PRO_SQUARE_R is 1e4 */
     bp.pro_super_r = PRO_SQUARE_R;
@@ -6608,7 +7332,18 @@ void BM_mesh_bevel(BMesh *bm,
     bp.vert_hash = BLI_ghash_ptr_new(__func__);
     bp.mem_arena = BLI_memarena_new(MEM_SIZE_OPTIMAL(1 << 16), __func__);
     BLI_memarena_use_calloc(bp.mem_arena);
-    set_profile_spacing(&bp);
+
+    /* Get the 2D profile point locations from either the superellipse or the custom profile */
+    set_profile_spacing(&bp, &bp.pro_spacing, bp.use_custom_profile);
+    if (bp.seg > 1) {
+      bp.pro_spacing.fullness = find_profile_fullness(&bp);
+    }
+
+    /* Get separate non-custom profile samples for the miter profiles if they are needed */
+    if (bp.use_custom_profile &&
+        (bp.miter_inner != BEVEL_MITER_SHARP || bp.miter_outer != BEVEL_MITER_SHARP)) {
+      set_profile_spacing(&bp, &bp.pro_spacing_miter, false);
+    }
 
     bp.face_hash = BLI_ghash_ptr_new(__func__);
     BLI_ghash_flag_set(bp.face_hash, GHASH_FLAG_ALLOW_DUPES);
@@ -6643,6 +7378,20 @@ void BM_mesh_bevel(BMesh *bm,
       adjust_offsets(&bp, bm);
     }
 
+    /* Maintain consistent orientations for the unsymmetrical custom profiles. */
+    if (bp.use_custom_profile) {
+      BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
+        if (BM_elem_flag_test(e, BM_ELEM_TAG)) {
+          regularize_profile_orientation(&bp, e);
+        }
+      }
+    }
+#ifdef DEBUG_PROFILE_ORIENTATION_DRAW
+    if (bp.use_custom_profile) {
+      debug_draw_profile_orientation(&bp, bm);
+    }
+#endif
+
     /* Build the meshes around vertices, now that positions are final */
     BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
       if (BM_elem_flag_test(v, BM_ELEM_TAG)) {
@@ -6688,14 +7437,14 @@ void BM_mesh_bevel(BMesh *bm,
     }
 
     if (bp.harden_normals) {
-      bevel_harden_normals(bm, &bp);
+      bevel_harden_normals(&bp, bm);
     }
     if (bp.face_strength_mode != BEVEL_FACE_STRENGTH_NONE) {
       bevel_set_weighted_normal_face_strength(bm, &bp);
     }
 
     /* When called from operator (as opposed to modifier), bm->use_toolflags
-     * will be set, and we to transfer the oflags to BM_ELEM_TAGs */
+     * will be set, and we need to transfer the oflags to BM_ELEM_TAGs */
     if (bm->use_toolflags) {
       BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
         if (BMO_vert_flag_test(bm, v, VERT_OUT)) {
@@ -6717,6 +7466,22 @@ void BM_mesh_bevel(BMesh *bm,
       BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
         BM_elem_flag_disable(l, BM_ELEM_LONG_TAG);
       }
+
+#ifdef DEBUG_CUSTOM_PROFILE_SAMPLE
+      printf("Profile spacing:\n");
+      printf("Seg values:\n");
+      if (bp.pro_spacing.xvals != NULL) {
+        for (int i = 0; i < bp.seg; i++) {
+          printf("(%.3f, %.3f)\n", bp.pro_spacing.xvals[i], bp.pro_spacing.yvals[i]);
+        }
+      }
+      if (bp.pro_spacing.seg_2 != bp.seg && bp.pro_spacing.seg_2 != 0) {
+        printf("Seg_2 values:\n");
+        for (int i = 0; i < bp.pro_spacing.seg_2; i++) {
+          printf("(%0.2f, %0.2f)\n", bp.pro_spacing.xvals_2[i], bp.pro_spacing.yvals_2[i]);
+        }
+      }
+#endif
     }
 
     /* primary free */
index 496be9219b78527cd56e8e6a132618271ffd854b..45bebbb33cece6568fb15c5613f83691e5744102 100644 (file)
@@ -21,6 +21,7 @@
  * \ingroup bmesh
  */
 
+struct CurveProfile;
 struct MDeformVert;
 
 void BM_mesh_bevel(BMesh *bm,
@@ -42,6 +43,8 @@ void BM_mesh_bevel(BMesh *bm,
                    const int miter_outer,
                    const int miter_inner,
                    const float spread,
-                   const float smoothresh);
-
+                   const float smoothresh,
+                   const bool use_custom_profile,
+                   const struct CurveProfile *custom_profile,
+                   const int vmesh_method);
 #endif /* __BMESH_BEVEL_H__ */
index 8f205173011c3fa25d3141e1f7bff3798d6a2c62..69229f25917833042beb01b8576eef280a3dd8d0 100644 (file)
@@ -346,6 +346,8 @@ typedef enum {
   /** sphere widget (used to input a unit-vector, aka normal) */
   UI_BTYPE_UNITVEC = 31 << 9,
   UI_BTYPE_CURVE = 32 << 9,
+  /** Profile editing widget */
+  UI_BTYPE_CURVEPROFILE = 33 << 9,
   UI_BTYPE_LISTBOX = 36 << 9,
   UI_BTYPE_LISTROW = 37 << 9,
   UI_BTYPE_HSVCIRCLE = 38 << 9,
@@ -1973,6 +1975,7 @@ void uiTemplateCurveMapping(uiLayout *layout,
                             bool brush,
                             bool neg_slope,
                             bool tone);
+void uiTemplateCurveProfile(uiLayout *layout, struct PointerRNA *ptr, const char *propname);
 void uiTemplateColorPicker(uiLayout *layout,
                            struct PointerRNA *ptr,
                            const char *propname,
index 72c31c7b39ef294e2c6e1b441537f45f4734b0ef..499842a570b5a80771a930e080ca1bb84a339a0a 100644 (file)
 #include "DNA_color_types.h"
 #include "DNA_screen_types.h"
 #include "DNA_movieclip_types.h"
+#include "DNA_curveprofile_types.h"
 
 #include "BLI_math.h"
 #include "BLI_rect.h"
 #include "BLI_string.h"
 #include "BLI_utildefines.h"
+#include "BLI_polyfill_2d.h"
+
+#include "MEM_guardedalloc.h"
 
 #include "BKE_colorband.h"
 #include "BKE_colortools.h"
 #include "BKE_node.h"
 #include "BKE_tracking.h"
+#include "BKE_curveprofile.h"
 
 #include "IMB_imbuf.h"
 #include "IMB_imbuf_types.h"
@@ -1814,6 +1819,8 @@ static void ui_draw_but_curve_grid(
     immVertex2f(pos, rect->xmax, fy);
     fy += dy;
   }
+  /* Note: Assertion fails with here when the view is moved farther below the center.
+   * Missing two points from the number given with immBegin. */
   immEnd();
 }
 
@@ -2113,6 +2120,231 @@ void ui_draw_but_CURVE(ARegion *ar, uiBut *but, const uiWidgetColors *wcol, cons
   immUnbindProgram();
 }
 
+/** Used to draw a curve profile widget. Somewhat similar to ui_draw_but_CURVE */
+void ui_draw_but_CURVEPROFILE(ARegion *ar,
+                              uiBut *but,
+                              const uiWidgetColors *wcol,
+                              const rcti *rect)
+{
+  uint i;
+  float fx, fy;
+  CurveProfile *profile;
+  if (but->editprofile) {
+    profile = but->editprofile;
+  }
+  else {
+    profile = (CurveProfile *)but->poin;
+  }
+
+  /* Calculate offset and zoom */
+  float zoomx = (BLI_rcti_size_x(rect) - 2.0f) / BLI_rctf_size_x(&profile->view_rect);
+  float zoomy = (BLI_rcti_size_y(rect) - 2.0f) / BLI_rctf_size_y(&profile->view_rect);
+  float offsx = profile->view_rect.xmin - (1.0f / zoomx);
+  float offsy = profile->view_rect.ymin - (1.0f / zoomy);
+
+  /* Exit early if too narrow */
+  if (zoomx == 0.0f) {
+    return;
+  }
+
+  /* Test needed because path can draw outside of boundary */
+  int scissor[4];
+  GPU_scissor_get_i(scissor);
+  rcti scissor_new = {
+      .xmin = rect->xmin,
+      .ymin = rect->ymin,
+      .xmax = rect->xmax,
+      .ymax = rect->ymax,
+  };
+  rcti scissor_region = {0, ar->winx, 0, ar->winy};
+  BLI_rcti_isect(&scissor_new, &scissor_region, &scissor_new);
+  GPU_scissor(scissor_new.xmin,
+              scissor_new.ymin,
+              BLI_rcti_size_x(&scissor_new),
+              BLI_rcti_size_y(&scissor_new));
+
+  GPU_line_width(1.0f);
+
+  GPUVertFormat *format = immVertexFormat();
+  uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
+  immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
+
+  /* Backdrop */
+  float color_backdrop[4] = {0, 0, 0, 1};
+  if (profile->flag & PROF_USE_CLIP) {
+    gl_shaded_color_get_fl((uchar *)wcol->inner, -20, color_backdrop);
+    immUniformColor3fv(color_backdrop);
+    immRectf(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax);
+    immUniformColor3ubv((uchar *)wcol->inner);
+    immRectf(pos,
+             rect->xmin + zoomx * (profile->clip_rect.xmin - offsx),
+             rect->ymin + zoomy * (profile->clip_rect.ymin - offsy),
+             rect->xmin + zoomx * (profile->clip_rect.xmax - offsx),
+             rect->ymin + zoomy * (profile->clip_rect.ymax - offsy));
+  }
+  else {
+    rgb_uchar_to_float(color_backdrop, (uchar *)wcol->inner);
+    immUniformColor3fv(color_backdrop);
+    immRectf(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax);
+  }
+
+  /* 0.25 step grid */
+  gl_shaded_color((uchar *)wcol->inner, -16);
+  ui_draw_but_curve_grid(pos, rect, zoomx, zoomy, offsx, offsy, 0.25f);
+  /* 1.0 step grid */
+  gl_shaded_color((uchar *)wcol->inner, -24);
+  ui_draw_but_curve_grid(pos, rect, zoomx, zoomy, offsx, offsy, 1.0f);
+
+  /* Draw the path's fill */
+  if (profile->table == NULL) {
+    BKE_curveprofile_update(profile, false);
+  }
+  CurveProfilePoint *pts = profile->table;
+  /* Also add the last points on the right and bottom edges to close off the fill polygon */
+  bool add_left_tri = profile->view_rect.xmin < 0.0f;
+  bool add_bottom_tri = profile->view_rect.ymin < 0.0f;
+  uint tot_points = (uint)PROF_N_TABLE(profile->path_len) + 1 + add_left_tri + add_bottom_tri;
+  uint tot_triangles = tot_points - 2;
+
+  /* Create array of the positions of the table's points */
+  float(*table_coords)[2] = MEM_mallocN(sizeof(*table_coords) * tot_points, "table x coords");
+  for (i = 0; i < (uint)PROF_N_TABLE(profile->path_len);
+       i++) { /* Only add the points from the table here */
+    table_coords[i][0] = pts[i].x;
+    table_coords[i][1] = pts[i].y;
+  }
+  if (add_left_tri && add_bottom_tri) {
+    /* Add left side, bottom left corner, and bottom side points */
+    table_coords[tot_points - 3][0] = profile->view_rect.xmin;
+    table_coords[tot_points - 3][1] = 1.0f;
+    table_coords[tot_points - 2][0] = profile->view_rect.xmin;
+    table_coords[tot_points - 2][1] = profile->view_rect.ymin;
+    table_coords[tot_points - 1][0] = 1.0f;
+    table_coords[tot_points - 1][1] = profile->view_rect.ymin;
+  }
+  else if (add_left_tri) {
+    /* Add the left side and bottom left corner points */
+    table_coords[tot_points - 2][0] = profile->view_rect.xmin;
+    table_coords[tot_points - 2][1] = 1.0f;
+    table_coords[tot_points - 1][0] = profile->view_rect.xmin;
+    table_coords[tot_points - 1][1] = 0.0f;
+  }
+  else if (add_bottom_tri) {
+    /* Add the bottom side and bottom left corner points */
+    table_coords[tot_points - 2][0] = 0.0f;
+    table_coords[tot_points - 2][1] = profile->view_rect.ymin;
+    table_coords[tot_points - 1][0] = 1.0f;
+    table_coords[tot_points - 1][1] = profile->view_rect.ymin;
+  }
+  else {
+    /* Just add the bottom corner point. Side points would be redundant anyway */
+    table_coords[tot_points - 1][0] = 0.0f;
+    table_coords[tot_points - 1][1] = 0.0f;
+  }
+
+  /* Calculate the table point indices of the triangles for the profile's fill */
+  uint(*tri_indices)[3] = MEM_mallocN(sizeof(*tri_indices) * tot_triangles, "return tri indices");
+  BLI_polyfill_calc(table_coords, tot_points, -1, tri_indices);
+
+  /* Draw the triangles for the profile fill */
+  immUniformColor3ubvAlpha((const uchar *)wcol->item, 128);
+  GPU_blend(true);
+  GPU_polygon_smooth(false);
+  immBegin(GPU_PRIM_TRIS, 3 * tot_triangles);
+  for (i = 0; i < tot_triangles; i++) {
+    for (uint j = 0; j < 3; j++) {
+      uint *tri = tri_indices[i];
+      fx = rect->xmin + zoomx * (table_coords[tri[j]][0] - offsx);
+      fy = rect->ymin + zoomy * (table_coords[tri[j]][1] - offsy);
+      immVertex2f(pos, fx, fy);
+    }
+  }
+  immEnd();
+  MEM_freeN(tri_indices);
+
+  /* Draw the profile's path so the edge stands out a bit */
+  tot_points -= (add_left_tri + add_left_tri);
+  GPU_line_width(1.0f);
+  immUniformColor3ubvAlpha((const uchar *)wcol->item, 255);
+  GPU_line_smooth(true);
+  immBegin(GPU_PRIM_LINE_STRIP, tot_points - 1);
+  for (i = 0; i < tot_points - 1; i++) {
+    fx = rect->xmin + zoomx * (table_coords[i][0] - offsx);
+    fy = rect->ymin + zoomy * (table_coords[i][1] - offsy);
+    immVertex2f(pos, fx, fy);
+  }
+  immEnd();
+  immUnbindProgram();
+  MEM_freeN(table_coords);
+
+  /* New GPU instructions for control points and sampled points. */
+  format = immVertexFormat();
+  pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
+  uint col = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT);
+  immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR);
+
+  /* Calculate vertex colors based on text theme. */
+  float color_vert[4], color_vert_select[4], color_sample[4];
+  UI_GetThemeColor4fv(TH_TEXT_HI, color_vert);
+  UI_GetThemeColor4fv(TH_TEXT, color_vert_select);
+  color_sample[0] = (float)wcol->item[0] / 255.0f;
+  color_sample[1] = (float)wcol->item[1] / 255.0f;
+  color_sample[2] = (float)wcol->item[2] / 255.0f;
+  color_sample[3] = (float)wcol->item[3] / 255.0f;
+  if (len_squared_v3v3(color_vert, color_vert_select) < 0.1f) {
+    interp_v3_v3v3(color_vert, color_vert_select, color_backdrop, 0.75f);
+  }
+  if (len_squared_v3(color_vert) > len_squared_v3(color_vert_select)) {
+    /* Ensure brightest text color is used for selection. */
+    swap_v3_v3(color_vert, color_vert_select);
+  }
+
+  /* Draw the control points. */
+  pts = profile->path;
+  tot_points = (uint)profile->path_len;
+  GPU_line_smooth(false);
+  GPU_blend(false);
+  GPU_point_size(max_ff(3.0f, min_ff(UI_DPI_FAC / but->block->aspect * 5.0f, 5.0f)));
+  immBegin(GPU_PRIM_POINTS, tot_points);
+  for (i = 0; i < tot_points; i++) {
+    fx = rect->xmin + zoomx * (pts[i].x - offsx);
+    fy = rect->ymin + zoomy * (pts[i].y - offsy);
+    immAttr4fv(col, (pts[i].flag & PROF_SELECT) ? color_vert_select : color_vert);
+    immVertex2f(pos, fx, fy);
+  }
+  immEnd();
+
+  /* Draw the sampled points in addition to the control points if they have been created */
+  pts = profile->segments;
+  tot_points = (uint)profile->segments_len;
+  if (tot_points > 0 && pts) {
+    GPU_point_size(max_ff(2.0f, min_ff(UI_DPI_FAC / but->block->aspect * 3.0f, 3.0f)));
+    immBegin(GPU_PRIM_POINTS, tot_points);
+    for (i = 0; i < tot_points; i++) {
+      fx = rect->xmin + zoomx * (pts[i].x - offsx);
+      fy = rect->ymin + zoomy * (pts[i].y - offsy);
+      immAttr4fv(col, color_sample);
+      immVertex2f(pos, fx, fy);
+    }
+    immEnd();
+  }
+
+  immUnbindProgram();
+
+  /* restore scissortest */
+  GPU_scissor(scissor[0], scissor[1], scissor[2], scissor[3]);
+
+  /* Outline */
+  format = immVertexFormat();
+  pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
+  immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
+
+  immUniformColor3ubv((const uchar *)wcol->outline);
+  imm_draw_box_wire_2d(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax);
+
+  immUnbindProgram();
+}
+
 void ui_draw_but_TRACKPREVIEW(ARegion *UNUSED(ar),
                               uiBut *but,
                               const uiWidgetColors *UNUSED(wcol),
index 806b5789df166d3fd43ec60cb8abaf7c0dd593e4..673e4c1a8da273e44197e09aed0a06a57bdf5e4c 100644 (file)
@@ -32,7 +32,7 @@
 #include "MEM_guardedalloc.h"
 
 #include "DNA_brush_types.h"
-
+#include "DNA_curveprofile_types.h"
 #include "DNA_scene_types.h"
 #include "DNA_screen_types.h"
 
@@ -57,6 +57,7 @@
 #include "BKE_tracking.h"
 #include "BKE_unit.h"
 #include "BKE_paint.h"
+#include "BKE_curveprofile.h"
 
 #include "IMB_colormanagement.h"
 
@@ -442,6 +443,8 @@ static uiButMultiState *ui_multibut_lookup(uiHandleButtonData *data, const uiBut
 static ColorBand but_copypaste_coba = {0};
 static CurveMapping but_copypaste_curve = {0};
 static bool but_copypaste_curve_alive = false;
+static CurveProfile but_copypaste_profile = {0};
+static bool but_copypaste_profile_alive = false;
 
 /** \} */
 
@@ -1080,6 +1083,13 @@ static void ui_apply_but_CURVE(bContext *C, uiBut *but, uiHandleButtonData *data
   data->applied = true;
 }
 
+static void ui_apply_but_CURVEPROFILE(bContext *C, uiBut *but, uiHandleButtonData *data)
+{
+  ui_apply_but_func(C, but);
+  data->retval = but->retval;
+  data->applied = true;
+}
+
 /** \} */
 
 /* -------------------------------------------------------------------- */
@@ -1979,6 +1989,7 @@ static void ui_apply_but(
   float *editvec;
   ColorBand *editcoba;
   CurveMapping *editcumap;
+  CurveProfile *editprofile;
 
   data->retval = 0;
 
@@ -2036,11 +2047,13 @@ static void ui_apply_but(
   editvec = but->editvec;
   editcoba = but->editcoba;
   editcumap = but->editcumap;
+  editprofile = but->editprofile;
   but->editstr = NULL;
   but->editval = NULL;
   but->editvec = NULL;
   but->editcoba = NULL;
   but->editcumap = NULL;
+  but->editprofile = NULL;
 
   /* handle different types */
   switch (but->type) {
@@ -2100,6 +2113,9 @@ static void ui_apply_but(
     case UI_BTYPE_CURVE:
       ui_apply_but_CURVE(C, but, data);
       break;
+    case UI_BTYPE_CURVEPROFILE:
+      ui_apply_but_CURVEPROFILE(C, but, data);
+      break;
     case UI_BTYPE_KEY_EVENT:
     case UI_BTYPE_HOTKEY_EVENT:
       ui_apply_but_BUT(C, but, data);
@@ -2147,6 +2163,7 @@ static void ui_apply_but(
   but->editvec = editvec;
   but->editcoba = editcoba;
   but->editcumap = editcumap;
+  but->editprofile = editprofile;
 }
 
 /** \} */
@@ -2446,6 +2463,29 @@ static void ui_but_paste_curvemapping(bContext *C, uiBut *but)
   }
 }
 
+static void ui_but_copy_CurveProfile(uiBut *but)
+{
+  if (but->poin != NULL) {
+    but_copypaste_profile_alive = true;
+    BKE_curveprofile_free_data(&but_copypaste_profile);
+    BKE_curveprofile_copy_data(&but_copypaste_profile, (CurveProfile *)but->poin);
+  }
+}
+
+static void ui_but_paste_CurveProfile(bContext *C, uiBut *but)
+{
+  if (but_copypaste_profile_alive) {
+    if (!but->poin) {
+      but->poin = MEM_callocN(sizeof(CurveProfile), "CurveProfile");
+    }
+
+    button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
+    BKE_curveprofile_free_data((CurveProfile *)but->poin);
+    BKE_curveprofile_copy_data((CurveProfile *)but->poin, &but_copypaste_profile);
+    button_activate_state(C, but, BUTTON_STATE_EXIT);
+  }
+}
+
 static void ui_but_copy_operator(bContext *C, uiBut *but, char *output, int output_len_max)
 {
   PointerRNA *opptr;
@@ -2540,6 +2580,10 @@ static void ui_but_copy(bContext *C, uiBut *but, const bool copy_array)
       ui_but_copy_curvemapping(but);
       break;
 
+    case UI_BTYPE_CURVEPROFILE:
+      ui_but_copy_CurveProfile(but);
+      break;
+
     case UI_BTYPE_BUT:
       if (!but->optype) {
         break;
@@ -2623,6 +2667,10 @@ static void ui_but_paste(bContext *C, uiBut *but, uiHandleButtonData *data, cons
       ui_but_paste_curvemapping(C, but);
       break;
 
+    case UI_BTYPE_CURVEPROFILE:
+      ui_but_paste_CurveProfile(C, but);
+      break;
+
     default:
       break;
   }
@@ -2633,6 +2681,7 @@ static void ui_but_paste(bContext *C, uiBut *but, uiHandleButtonData *data, cons
 void ui_but_clipboard_free(void)
 {
   BKE_curvemapping_free_data(&but_copypaste_curve);
+  BKE_curveprofile_free_data(&but_copypaste_profile);
 }
 
 /** \} */
@@ -3757,6 +3806,9 @@ static void ui_numedit_begin(uiBut *but, uiHandleButtonData *data)
   if (but->type == UI_BTYPE_CURVE) {
     but->editcumap = (CurveMapping *)but->poin;
   }
+  if (but->type == UI_BTYPE_CURVEPROFILE) {
+    but->editprofile = (CurveProfile *)but->poin;
+  }
   else if (but->type == UI_BTYPE_COLORBAND) {
     data->coba = (ColorBand *)but->poin;
     but->editcoba = data->coba;
@@ -3847,6 +3899,7 @@ static void ui_numedit_end(uiBut *but, uiHandleButtonData *data)
   but->editvec = NULL;
   but->editcoba = NULL;
   but->editcumap = NULL;
+  but->editprofile = NULL;
 
   data->dragstartx = 0;
   data->draglastx = 0;
@@ -6803,6 +6856,281 @@ static int ui_do_but_CURVE(
   return WM_UI_HANDLER_CONTINUE;
 }
 
+/* Same as ui_numedit_but_CURVE with some smaller changes. */
+static bool ui_numedit_but_CURVEPROFILE(uiBlock *block,
+                                        uiBut *but,
+                                        uiHandleButtonData *data,
+                                        int evtx,
+                                        int evty,
+                                        bool snap,
+                                        const bool shift)
+{
+  CurveProfile *profile = (CurveProfile *)but->poin;
+  CurveProfilePoint *pts = profile->path;
+  float fx, fy, zoomx, zoomy;
+  int mx, my, dragx, dragy;
+  int a;
+  bool changed = false;
+
+  /* evtx evty and drag coords are absolute mousecoords,
+   * prevents errors when editing when layout changes */
+  mx = evtx;
+  my = evty;
+  ui_window_to_block(data->region, block, &mx, &my);
+  dragx = data->draglastx;
+  dragy = data->draglasty;
+  ui_window_to_block(data->region, block, &dragx, &dragy);
+
+  zoomx = BLI_rctf_size_x(&but->rect) / BLI_rctf_size_x(&profile->view_rect);
+  zoomy = BLI_rctf_size_y(&but->rect) / BLI_rctf_size_y(&profile->view_rect);
+
+  if (snap) {
+    float d[2];
+
+    d[0] = mx - data->dragstartx;
+    d[1] = my - data->dragstarty;
+
+    if (len_squared_v2(d) < (3.0f * 3.0f)) {
+      snap = false;
+    }
+  }
+
+  fx = (mx - dragx) / zoomx;
+  fy = (my - dragy) / zoomy;
+
+  if (data->dragsel != -1) {
+    CurveProfilePoint *point_last = NULL;
+    const float mval_factor = ui_mouse_scale_warp_factor(shift);
+    bool moved_point = false; /* for ctrl grid, can't use orig coords because of sorting */
+
+    fx *= mval_factor;
+    fy *= mval_factor;
+
+    /* Move all the points that aren't the last or the first */
+    for (a = 1; a < profile->path_len - 1; a++) {
+      if (pts[a].flag & PROF_SELECT) {
+        float origx = pts[a].x, origy = pts[a].y;
+        pts[a].x += fx;
+        pts[a].y += fy;
+        if (snap) {
+          pts[a].x = 0.125f * roundf(8.0f * pts[a].x);
+          pts[a].y = 0.125f * roundf(8.0f * pts[a].y);
+        }
+        if (!moved_point && (pts[a].x != origx || pts[a].y != origy)) {
+          moved_point = true;
+        }
+
+        point_last = &pts[a];
+      }
+    }
+
+    BKE_curveprofile_update(profile, false);
+
+    if (moved_point) {
+      data->draglastx = evtx;
+      data->draglasty = evty;
+      changed = true;
+#ifdef USE_CONT_MOUSE_CORRECT
+      /* note: using 'cmp_last' is weak since there may be multiple points selected,
+       * but in practice this isnt really an issue */
+      if (ui_but_is_cursor_warp(but)) {
+        /* OK but can go outside bounds */
+        data->ungrab_mval[0] = but->rect.xmin + ((point_last->x - profile->view_rect.xmin) * zoomx);
+        data->ungrab_mval[1] = but->rect.ymin + ((point_last->y - profile->view_rect.ymin) * zoomy);
+        BLI_rctf_clamp_pt_v(&but->rect, data->ungrab_mval);
+      }
+#endif
+    }
+    data->dragchange = true; /* mark for selection */
+  }
+  else {
+    /* clamp for clip */
+    if (profile->flag & PROF_USE_CLIP) {
+      if (profile->view_rect.xmin - fx < profile->clip_rect.xmin) {
+        fx = profile->view_rect.xmin - profile->clip_rect.xmin;
+      }
+      else if (profile->view_rect.xmax - fx > profile->clip_rect.xmax) {
+        fx = profile->view_rect.xmax - profile->clip_rect.xmax;
+      }
+      if (profile->view_rect.ymin - fy < profile->clip_rect.ymin) {
+        fy = profile->view_rect.ymin - profile->clip_rect.ymin;
+      }
+      else if (profile->view_rect.ymax - fy > profile->clip_rect.ymax) {
+        fy = profile->view_rect.ymax - profile->clip_rect.ymax;
+      }
+    }
+
+    profile->view_rect.xmin -= fx;
+    profile->view_rect.ymin -= fy;
+    profile->view_rect.xmax -= fx;
+    profile->view_rect.ymax -= fy;
+
+    data->draglastx = evtx;
+    data->draglasty = evty;
+
+    changed = true;
+  }
+
+  return changed;
+}
+
+/** Interaction for curve profile widget.
+ * \note Uses hardcoded keys rather than the keymap. */
+static int ui_do_but_CURVEPROFILE(
+    bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
+{
+  int mx, my, i;
+
+  mx = event->x;
+  my = event->y;
+  ui_window_to_block(data->region, block, &mx, &my);
+
+  /* Move selected control points. */
+  if (event->type == GKEY && event->val == KM_RELEASE) {
+    data->dragstartx = mx;
+    data->dragstarty = my;
+    data->draglastx = mx;
+    data->draglasty = my;
+    button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
+    return WM_UI_HANDLER_BREAK;
+  }
+
+  CurveProfile *profile = (CurveProfile *)but->poin;
+
+  /* Delete selected control points. */
+  if (event->type == XKEY && event->val == KM_RELEASE) {
+    BKE_curveprofile_remove_by_flag(profile, PROF_SELECT);
+    BKE_curveprofile_update(profile, false);
+    button_activate_state(C, but, BUTTON_STATE_EXIT);
+    return WM_UI_HANDLER_BREAK;
+  }
+
+  /* Selecting, adding, and starting point movements. */
+  if (data->state == BUTTON_STATE_HIGHLIGHT) {
+    if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
+      CurveProfilePoint *pts; /* Path or table. */
+      const float m_xy[2] = {mx, my};
+      float dist_min_sq;
+      int i_selected = -1;
+
+      if (event->ctrl) {
+        float f_xy[2];
+        BLI_rctf_transform_pt_v(&profile->view_rect, &but->rect, f_xy, m_xy);
+
+        BKE_curveprofile_insert(profile, f_xy[0], f_xy[1]);
+        BKE_curveprofile_update(profile, false);
+      }
+
+      /* Check for selecting of a point by finding closest point in radius. */
+      dist_min_sq = SQUARE(U.dpi_fac * 14.0f); /* 14 pixels radius for selecting points. */
+      pts = profile->path;
+      for (i = 0; i < profile->path_len; i++) {
+        float f_xy[2];
+        BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, &pts[i].x);
+        const float dist_sq = len_squared_v2v2(m_xy, f_xy);
+        if (dist_sq < dist_min_sq) {
+          i_selected = i;
+          dist_min_sq = dist_sq;
+        }
+      }
+
+      /* Add a point if the click was close to the path but not a control point. */
+      if (i_selected == -1) { /* No control point selected. */
+        float f_xy[2], f_xy_prev[2];
+        pts = profile->table;
+        BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, &pts[0].x);
+
+        dist_min_sq = SQUARE(U.dpi_fac * 8.0f); /* 8 pixel radius from each table point. */
+
+        /* Loop through the path's high resolution table and find what's near the click. */
+        for (i = 1; i <= PROF_N_TABLE(profile->path_len); i++) {
+          copy_v2_v2(f_xy_prev, f_xy);
+          BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, &pts[i].x);
+
+          if (dist_squared_to_line_segment_v2(m_xy, f_xy_prev, f_xy) < dist_min_sq) {
+            BLI_rctf_transform_pt_v(&profile->view_rect, &but->rect, f_xy, m_xy);
+
+            CurveProfilePoint *new_pt = BKE_curveprofile_insert(profile, f_xy[0], f_xy[1]);
+            BKE_curveprofile_update(profile, false);
+
+            /* reset pts back to the control points. */
+            pts = profile->path;
+
+            /* Get the index of the newly added point. */
+            for (i = 0; i < profile->path_len; i++) {
+              if (&pts[i] == new_pt) {
+                i_selected = i;
+              }
+            }
+            break;
+          }
+        }
+      }
+
+      /* Change the flag for the point(s) if one was selected. */
+      if (i_selected != -1) {
+        /* Deselect all if this one is deselected, except if we hold shift. */
+        if (!event->shift) {
+          for (i = 0; i < profile->path_len; i++) {
+            pts[i].flag &= ~PROF_SELECT;
+          }
+          pts[i_selected].flag |= PROF_SELECT;
+        }
+        else {
+          pts[i_selected].flag ^= PROF_SELECT;
+        }
+      }
+      else {
+        /* Move the view. */
+        data->cancel = true;
+      }
+
+      data->dragsel = i_selected;
+
+      data->dragstartx = mx;
+      data->dragstarty = my;
+      data->draglastx = mx;
+      data->draglasty = my;
+
+      button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
+      return WM_UI_HANDLER_BREAK;
+    }
+  }
+  else if (data->state == BUTTON_STATE_NUM_EDITING) { /* Do control point movement. */
+    if (event->type == MOUSEMOVE) {
+      if (mx != data->draglastx || my != data->draglasty) {
+        if (ui_numedit_but_CURVEPROFILE(
+                block, but, data, mx, my, event->ctrl != 0, event->shift != 0)) {
+          ui_numedit_apply(C, block, but, data);
+        }
+      }
+    }
+    else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
+      /* Finish move. */
+      if (data->dragsel != -1) {
+        CurveProfilePoint *pts = profile->path;
+
+        if (data->dragchange == false) {
+          /* Deselect all, select one. */
+          if (!event->shift) {
+            for (i = 0; i < profile->path_len; i++) {
+              pts[i].flag &= ~PROF_SELECT;
+            }
+            pts[data->dragsel].flag |= PROF_SELECT;
+          }
+        }
+        else {
+          BKE_curveprofile_update(profile, true); /* Remove doubles after move. */
+        }
+      }
+      button_activate_state(C, but, BUTTON_STATE_EXIT);
+    }
+    return WM_UI_HANDLER_BREAK;
+  }
+
+  return WM_UI_HANDLER_CONTINUE;
+}
+
 static bool ui_numedit_but_HISTOGRAM(uiBut *but, uiHandleButtonData *data, int mx, int my)
 {
   Histogram *hist = (Histogram *)but->poin;
@@ -7208,6 +7536,9 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *
     case UI_BTYPE_CURVE:
       retval = ui_do_but_CURVE(C, block, but, data, event);
       break;
+    case UI_BTYPE_CURVEPROFILE:
+      retval = ui_do_but_CURVEPROFILE(C, block, but, data, event);
+      break;
     case UI_BTYPE_HSVCUBE:
       retval = ui_do_but_HSVCUBE(C, block, but, data, event);
       break;
@@ -7599,7 +7930,7 @@ static void button_activate_init(bContext *C, ARegion *ar, uiBut *but, uiButtonA
   copy_v2_fl(data->ungrab_mval, FLT_MAX);
 #endif
 
-  if (ELEM(but->type, UI_BTYPE_CURVE, UI_BTYPE_SEARCH_MENU)) {
+  if (ELEM(but->type, UI_BTYPE_CURVE, UI_BTYPE_CURVEPROFILE, UI_BTYPE_SEARCH_MENU)) {
     /* XXX curve is temp */
   }
   else {
index 81979b1fc8f790e3634999a20a459872636a5fe3..3fe2750e070f26c0d4268785c158b28e101fb13a 100644 (file)
@@ -274,6 +274,7 @@ struct uiBut {
   float *editvec;
   void *editcoba;
   void *editcumap;
+  void *editprofile;
 
   uiButPushedStateFunc pushed_state_func;
   void *pushed_state_arg;
@@ -740,6 +741,10 @@ void ui_draw_but_CURVE(ARegion *ar,
                        uiBut *but,
                        const struct uiWidgetColors *wcol,
                        const rcti *rect);
+void ui_draw_but_CURVEPROFILE(ARegion *ar,
+                              uiBut *but,
+                              const struct uiWidgetColors *wcol,
+                              const rcti *rect);
 void ui_draw_but_IMAGE(ARegion *ar,
                        uiBut *but,
                        const struct uiWidgetColors *wcol,
index 34b1070f8b465e1cd3ad5e15912606907588f85e..ab20b5ac5200adedb0dd3971e5620facd7a71639 100644 (file)
@@ -422,7 +422,8 @@ bool ui_but_is_cursor_warp(const uiBut *but)
              UI_BTYPE_HSVCIRCLE,
              UI_BTYPE_TRACK_PREVIEW,
              UI_BTYPE_HSVCUBE,
-             UI_BTYPE_CURVE)) {
+             UI_BTYPE_CURVE,
+             UI_BTYPE_CURVEPROFILE)) {
       return true;
     }
   }
index 021d7733fae80ae7a58f6f478128a486affe4b7c..b205572ba06d5485f2df488583b619e78e4dba35 100644 (file)
@@ -35,6 +35,7 @@
 #include "DNA_texture_types.h"
 #include "DNA_gpencil_modifier_types.h"
 #include "DNA_shader_fx_types.h"
+#include "DNA_curveprofile_types.h"
 
 #include "BLI_utildefines.h"
 #include "BLI_alloca.h"
@@ -69,6 +70,7 @@
 #include "BKE_packedFile.h"
 #include "BKE_paint.h"
 #include "BKE_particle.h"
+#include "BKE_curveprofile.h"
 #include "BKE_report.h"
 #include "BKE_screen.h"
 #include "BKE_shader_fx.h"
@@ -4504,6 +4506,612 @@ void uiTemplateCurveMapping(uiLayout *layout,
 
 /** \} */
 
+/* -------------------------------------------------------------------- */
+/** \name Curve Profile Template
+ * \{ */
+
+static void CurveProfile_presets_dofunc(bContext *C, void *profile_v, int event)
+{
+  CurveProfile *profile = profile_v;
+
+  profile->preset = event;
+  BKE_curveprofile_reset(profile);
+  BKE_curveprofile_update(profile, false);
+
+  ED_undo_push(C, "CurveProfile tools");
+  ED_region_tag_redraw(CTX_wm_region(C));
+}
+
+static uiBlock *CurveProfile_presets_func(bContext *C, ARegion *ar, CurveProfile *profile)
+{
+  uiBlock *block;
+  short yco = 0;
+  short menuwidth = 12 * UI_UNIT_X;
+  menuwidth = 0;
+
+  block = UI_block_begin(C, ar, __func__, UI_EMBOSS);
+  UI_block_func_butmenu_set(block, CurveProfile_presets_dofunc, profile);
+
+  uiDefIconTextBut(block,
+                   UI_BTYPE_BUT_MENU,
+                   1,
+                   ICON_BLANK1,
+                   IFACE_("Default"),
+                   0,
+                   yco -= UI_UNIT_Y,
+                   menuwidth,
+                   UI_UNIT_Y,
+                   NULL,
+                   0.0,
+                   0.0,
+                   0,
+                   PROF_PRESET_LINE,
+                   "");
+  uiDefIconTextBut(block,
+                   UI_BTYPE_BUT_MENU,
+                   1,
+                   ICON_BLANK1,
+                   IFACE_("Support Loops"),
+                   0,
+                   yco -= UI_UNIT_Y,
+                   menuwidth,
+                   UI_UNIT_Y,
+                   NULL,
+                   0.0,
+                   0.0,
+                   0,
+                   PROF_PRESET_SUPPORTS,
+                   "");
+  uiDefIconTextBut(block,
+                   UI_BTYPE_BUT_MENU,
+                   1,
+                   ICON_BLANK1,
+                   IFACE_("Cornice Moulding"),
+                   0,
+                   yco -= UI_UNIT_Y,
+                   menuwidth,
+                   UI_UNIT_Y,
+                   NULL,
+                   0.0,
+                   0.0,
+                   0,
+                   PROF_PRESET_CORNICE,
+                   "");
+  uiDefIconTextBut(block,
+                   UI_BTYPE_BUT_MENU,
+                   1,
+                   ICON_BLANK1,
+                   IFACE_("Crown Moulding"),
+                   0,
+                   yco -= UI_UNIT_Y,
+                   menuwidth,
+                   UI_UNIT_Y,
+                   NULL,
+                   0.0,
+                   0.0,
+                   0,
+                   PROF_PRESET_CROWN,
+                   "");
+  uiDefIconTextBut(block,
+                   UI_BTYPE_BUT_MENU,
+                   1,
+                   ICON_BLANK1,
+                   IFACE_("Steps"),
+                   0,
+                   yco -= UI_UNIT_Y,
+                   menuwidth,
+                   UI_UNIT_Y,
+                   NULL,
+                   0.0,
+                   0.0,
+                   0,
+                   PROF_PRESET_STEPS,
+                   "");
+
+  UI_block_direction_set(block, UI_DIR_DOWN);
+  UI_block_bounds_set_text(block, (int)(3.0f * UI_UNIT_X));
+
+  return block;
+}
+
+static uiBlock *CurveProfile_buttons_presets(bContext *C, ARegion *ar, void *profile_v)
+{
+  return CurveProfile_presets_func(C, ar, (CurveProfile *)profile_v);
+}
+
+/* Only for CurveProfile tools block */
+enum {
+  UIPROFILE_FUNC_RESET,
+  UIPROFILE_FUNC_RESET_VIEW,
+};
+
+static void CurveProfile_tools_dofunc(bContext *C, void *profile_v, int event)
+{
+  CurveProfile *profile = profile_v;
+
+  switch (event) {
+    case UIPROFILE_FUNC_RESET: /* reset */
+      BKE_curveprofile_reset(profile);
+      BKE_curveprofile_update(profile, false);
+      break;
+    case UIPROFILE_FUNC_RESET_VIEW: /* reset view to clipping rect */
+      profile->view_rect = profile->clip_rect;
+      break;
+  }
+  ED_undo_push(C, "CurveProfile tools");
+  ED_region_tag_redraw(CTX_wm_region(C));
+}
+
+static uiBlock *CurveProfile_tools_func(bContext *C, ARegion *ar, CurveProfile *profile)
+{
+  uiBlock *block;
+  short yco = 0;
+  short menuwidth = 10 * UI_UNIT_X;
+
+  block = UI_block_begin(C, ar, __func__, UI_EMBOSS);
+  UI_block_func_butmenu_set(block, CurveProfile_tools_dofunc, profile);
+
+  uiDefIconTextBut(block,
+                   UI_BTYPE_BUT_MENU,
+                   1,
+                   ICON_BLANK1,
+                   IFACE_("Reset View"),
+                   0,
+                   yco -= UI_UNIT_Y,
+                   menuwidth,
+                   UI_UNIT_Y,
+                   NULL,
+                   0.0,
+                   0.0,
+                   0,
+                   UIPROFILE_FUNC_RESET_VIEW,
+                   "");
+  uiDefIconTextBut(block,
+                   UI_BTYPE_BUT_MENU,
+                   1,
+                   ICON_BLANK1,
+                   IFACE_("Reset Curve"),
+                   0,
+                   yco -= UI_UNIT_Y,
+                   menuwidth,
+                   UI_UNIT_Y,
+                   NULL,
+                   0.0,
+                   0.0,
+                   0,
+                   UIPROFILE_FUNC_RESET,
+                   "");
+
+  UI_block_direction_set(block, UI_DIR_DOWN);
+  UI_block_bounds_set_text(block, (int)(3.0f * UI_UNIT_X));
+
+  return block;
+}
+
+static uiBlock *CurveProfile_buttons_tools(bContext *C, ARegion *ar, void *profile_v)
+{
+  return CurveProfile_tools_func(C, ar, (CurveProfile *)profile_v);
+}
+
+static void CurveProfile_buttons_zoom_in(bContext *C, void *profile_v, void *UNUSED(arg))
+{
+  CurveProfile *profile = profile_v;
+  float d;
+
+  /* we allow 20 times zoom */
+  if (BLI_rctf_size_x(&profile->view_rect) > 0.04f * BLI_rctf_size_x(&profile->clip_rect)) {
+    d = 0.1154f * BLI_rctf_size_x(&profile->view_rect);
+    profile->view_rect.xmin += d;
+    profile->view_rect.xmax -= d;
+    d = 0.1154f * BLI_rctf_size_y(&profile->view_rect);
+    profile->view_rect.ymin += d;
+    profile->view_rect.ymax -= d;
+  }
+
+  ED_region_tag_redraw(CTX_wm_region(C));
+}
+
+static void CurveProfile_buttons_zoom_out(bContext *C, void *profile_v, void *UNUSED(arg))
+{
+  CurveProfile *profile = profile_v;
+  float d, d1;
+
+  /* Allow 20 times zoom, but don't view outside clip */
+  if (BLI_rctf_size_x(&profile->view_rect) < 20.0f * BLI_rctf_size_x(&profile->clip_rect)) {
+    d = d1 = 0.15f * BLI_rctf_size_x(&profile->view_rect);
+
+    if (profile->flag & PROF_USE_CLIP) {
+      if (profile->view_rect.xmin - d < profile->clip_rect.xmin) {
+        d1 = profile->view_rect.xmin - profile->clip_rect.xmin;
+      }
+    }
+    profile->view_rect.xmin -= d1;
+
+    d1 = d;
+    if (profile->flag & PROF_USE_CLIP) {
+      if (profile->view_rect.xmax + d > profile->clip_rect.xmax) {
+        d1 = -profile->view_rect.xmax + profile->clip_rect.xmax;
+      }
+    }
+    profile->view_rect.xmax += d1;
+
+    d = d1 = 0.15f * BLI_rctf_size_y(&profile->view_rect);
+
+    if (profile->flag & PROF_USE_CLIP) {
+      if (profile->view_rect.ymin - d < profile->clip_rect.ymin) {
+        d1 = profile->view_rect.ymin - profile->clip_rect.ymin;
+      }
+    }
+    profile->view_rect.ymin -= d1;
+
+    d1 = d;
+    if (profile->flag & PROF_USE_CLIP) {
+      if (profile->view_rect.ymax + d > profile->clip_rect.ymax) {
+        d1 = -profile->view_rect.ymax + profile->clip_rect.ymax;
+      }
+    }
+    profile->view_rect.ymax += d1;
+  }
+
+  ED_region_tag_redraw(CTX_wm_region(C));
+}
+
+static void CurveProfile_clipping_toggle(bContext *C, void *cb_v, void *profile_v)
+{
+  CurveProfile *profile = profile_v;
+
+  profile->flag ^= PROF_USE_CLIP;
+
+  BKE_curveprofile_update(profile, false);
+  rna_update_cb(C, cb_v, NULL);
+}
+
+static void CurveProfile_buttons_reverse(bContext *C, void *cb_v, void *profile_v)
+{
+  CurveProfile *profile = profile_v;
+
+  BKE_curveprofile_reverse(profile);
+  BKE_curveprofile_update(profile, false);
+  rna_update_cb(C, cb_v, NULL);
+}
+
+static void CurveProfile_buttons_delete(bContext *C, void *cb_v, void *profile_v)
+{
+  CurveProfile *profile = profile_v;
+
+  BKE_curveprofile_remove_by_flag(profile, SELECT);
+  BKE_curveprofile_update(profile, false);
+
+  rna_update_cb(C, cb_v, NULL);
+}
+
+static void CurveProfile_buttons_setsharp(bContext *C, void *cb_v, void *profile_v)
+{
+  CurveProfile *profile = profile_v;
+
+  BKE_curveprofile_selected_handle_set(profile, HD_VECT, HD_VECT);
+  BKE_curveprofile_update(profile, false);
+
+  rna_update_cb(C, cb_v, NULL);
+}
+
+static void CurveProfile_buttons_setcurved(bContext *C, void *cb_v, void *profile_v)
+{
+  CurveProfile *profile = profile_v;
+
+  BKE_curveprofile_selected_handle_set(profile, HD_AUTO, HD_AUTO);
+  BKE_curveprofile_update(profile, false);
+
+  rna_update_cb(C, cb_v, NULL);
+}
+
+static void CurveProfile_buttons_update(bContext *C, void *arg1_v, void *profile_v)
+{
+  CurveProfile *profile = profile_v;
+  BKE_curveprofile_update(profile, true);
+  rna_update_cb(C, arg1_v, NULL);
+}
+
+static void CurveProfile_buttons_layout(uiLayout *layout, PointerRNA *ptr, RNAUpdateCb *cb)
+{
+  CurveProfile *profile = ptr->data;
+  CurveProfilePoint *point = NULL;
+  uiLayout *row, *sub;
+  uiBlock *block;
+  uiBut *bt;
+  int i, icon, path_width, path_height;
+  bool point_last_or_first = false;
+  rctf bounds;
+
+  block = uiLayoutGetBlock(layout);
+
+  UI_block_emboss_set(block, UI_EMBOSS);
+
+  uiLayoutRow(layout, false);
+
+  /* Preset selector */
+  /* There is probably potential to use simpler "uiItemR" functions here, but automatic updating
+   * after a preset is selected would be more complicated. */
+  bt = uiDefBlockBut(
+      block, CurveProfile_buttons_presets, profile, "Preset", 0, 0, UI_UNIT_X, UI_UNIT_X, "");
+  UI_but_funcN_set(bt, rna_update_cb, MEM_dupallocN(cb), NULL);
+
+  row = uiLayoutRow(layout, false);
+
+  /* (Left aligned) */
+  sub = uiLayoutRow(row, true);
+  uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_LEFT);
+
+  /* Zoom in */
+  bt = uiDefIconBut(block,
+                    UI_BTYPE_BUT,
+                    0,
+                    ICON_ZOOM_IN,
+                    0,
+                    0,
+                    UI_UNIT_X,
+                    UI_UNIT_X,
+                    NULL,
+                    0.0,
+                    0.0,
+                    0.0,
+                    0.0,
+                    TIP_("Zoom in"));
+  UI_but_func_set(bt, CurveProfile_buttons_zoom_in, profile, NULL);
+
+  /* Zoom out */
+  bt = uiDefIconBut(block,
+                    UI_BTYPE_BUT,
+                    0,
+                    ICON_ZOOM_OUT,
+                    0,
+                    0,
+                    UI_UNIT_X,
+                    UI_UNIT_X,
+                    NULL,
+                    0.0,
+                    0.0,
+                    0.0,
+                    0.0,
+                    TIP_("Zoom out"));
+  UI_but_func_set(bt, CurveProfile_buttons_zoom_out, profile, NULL);
+
+  /* (Right aligned) */
+  sub = uiLayoutRow(row, true);
+  uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_RIGHT);
+
+  /* Reset view, reset curve */
+  bt = uiDefIconBlockBut(
+      block, CurveProfile_buttons_tools, profile, 0, 0, 0, 0, UI_UNIT_X, UI_UNIT_X, TIP_("Tools"));
+  UI_but_funcN_set(bt, rna_update_cb, MEM_dupallocN(cb), NULL);
+
+  /* Flip path */
+  bt = uiDefIconBut(block,
+                    UI_BTYPE_BUT,
+                    0,
+                    ICON_ARROW_LEFTRIGHT,
+                    0,
+                    0,
+                    UI_UNIT_X,
+                    UI_UNIT_X,
+                    NULL,
+                    0.0,
+                    0.0,
+                    0.0,
+                    0.0,
+                    TIP_("Reverse Path"));
+  UI_but_funcN_set(bt, CurveProfile_buttons_reverse, MEM_dupallocN(cb), profile);
+
+  /* Clipping toggle */
+  icon = (profile->flag & PROF_USE_CLIP) ? ICON_CLIPUV_HLT : ICON_CLIPUV_DEHLT;
+  bt = uiDefIconBut(block,
+                    UI_BTYPE_BUT,
+                    0,
+                    icon,
+                    0,
+                    0,
+                    UI_UNIT_X,
+                    UI_UNIT_X,
+                    NULL,
+                    0.0,
+                    0.0,
+                    0.0,
+                    0.0,
+                    TIP_("Toggle Profile Clipping"));
+  UI_but_funcN_set(bt, CurveProfile_clipping_toggle, MEM_dupallocN(cb), profile);
+
+  UI_block_funcN_set(block, rna_update_cb, MEM_dupallocN(cb), NULL);
+
+  /* The path itself */
+  path_width = max_ii(uiLayoutGetWidth(layout), UI_UNIT_X);
+  path_width = min_ii(path_width, (int)(16.0f * UI_UNIT_X));
+  path_height = path_width;
+  uiLayoutRow(layout, false);
+  uiDefBut(block,
+           UI_BTYPE_CURVEPROFILE,
+           0,
+           "",
+           0,
+           0,
+           (short)path_width,
+           (short)path_height,
+           profile,
+           0.0f,
+           1.0f,
+           -1,
+           0,
+           "");
+
+  /* Position sliders for (first) selected point */
+  for (i = 0; i < profile->path_len; i++) {
+    if (profile->path[i].flag & PROF_SELECT) {
+      point = &profile->path[i];
+      break;
+    }
+  }
+  if (i == 0 || i == profile->path_len - 1) {
+    point_last_or_first = true;
+  }
+
+  /* Selected point data */
+  if (point) {
+    if (profile->flag & PROF_USE_CLIP) {
+      bounds = profile->clip_rect;
+    }
+    else {
+      bounds.xmin = bounds.ymin = -1000.0;
+      bounds.xmax = bounds.ymax = 1000.0;
+    }
+
+    uiLayoutRow(layout, true);
+    UI_block_funcN_set(block, CurveProfile_buttons_update, MEM_dupallocN(cb), profile);
+
+    /* Sharp / Smooth */
+    bt = uiDefIconBut(block,
+                      UI_BTYPE_BUT,
+                      0,
+                      ICON_LINCURVE,
+                      0,
+                      0,
+                      UI_UNIT_X,
+                      UI_UNIT_X,
+                      NULL,
+                      0.0,
+                      0.0,
+                      0.0,
+                      0.0,
+                      TIP_("Set the point's handle type to sharp."));
+    if (point_last_or_first) {
+      UI_but_flag_enable(bt, UI_BUT_DISABLED);
+    }
+    UI_but_funcN_set(bt, CurveProfile_buttons_setsharp, MEM_dupallocN(cb), profile);
+    bt = uiDefIconBut(block,
+                      UI_BTYPE_BUT,
+                      0,
+                      ICON_SMOOTHCURVE,
+                      0,
+                      0,
+                      UI_UNIT_X,
+                      UI_UNIT_X,
+                      NULL,
+                      0.0,
+                      0.0,
+                      0.0,
+                      0.0,
+                      TIP_("Set the point's handle type to sharp."));
+    UI_but_funcN_set(bt, CurveProfile_buttons_setcurved, MEM_dupallocN(cb), profile);
+    if (point_last_or_first) {
+      UI_but_flag_enable(bt, UI_BUT_DISABLED);
+    }
+
+    /* Position */
+    bt = uiDefButF(block,
+                   UI_BTYPE_NUM,
+                   0,
+                   "X:",
+                   0,
+                   2 * UI_UNIT_Y,
+                   UI_UNIT_X * 10,
+                   UI_UNIT_Y,
+                   &point->x,
+                   bounds.xmin,
+                   bounds.xmax,
+                   1,
+                   5,
+                   "");
+    if (point_last_or_first) {
+      UI_but_flag_enable(bt, UI_BUT_DISABLED);
+    }
+
+    bt = uiDefButF(block,
+                   UI_BTYPE_NUM,
+                   0,
+                   "Y:",
+                   0,
+                   1 * UI_UNIT_Y,
+                   UI_UNIT_X * 10,
+                   UI_UNIT_Y,
+                   &point->y,
+                   bounds.ymin,
+                   bounds.ymax,
+                   1,
+                   5,
+                   "");
+    if (point_last_or_first) {
+      UI_but_flag_enable(bt, UI_BUT_DISABLED);
+    }
+
+    /* Delete points */
+    bt = uiDefIconBut(block,
+                      UI_BTYPE_BUT,
+                      0,
+                      ICON_X,
+                      0,
+                      0,
+                      UI_UNIT_X,
+                      UI_UNIT_X,
+                      NULL,
+                      0.0,
+                      0.0,
+                      0.0,
+                      0.0,
+                      TIP_("Delete points"));
+    UI_but_funcN_set(bt, CurveProfile_buttons_delete, MEM_dupallocN(cb), profile);
+    if (point_last_or_first) {
+      UI_but_flag_enable(bt, UI_BUT_DISABLED);
+    }
+  }
+
+  uiItemR(layout, ptr, "use_sample_straight_edges", 0, NULL, ICON_NONE);
+  uiItemR(layout, ptr, "use_sample_even_lengths", 0, NULL, ICON_NONE);
+
+  UI_block_funcN_set(block, NULL, NULL, NULL);
+}
+
+/** Template for a path creation widget intended for custom bevel profiles.
+ * This section is quite similar to uiTemplateCurveMapping, but with reduced complexity */
+void uiTemplateCurveProfile(uiLayout *layout, PointerRNA *ptr, const char *propname)
+{
+  RNAUpdateCb *cb;
+  PropertyRNA *prop = RNA_struct_find_property(ptr, propname);
+  PointerRNA cptr;
+  ID *id;
+  uiBlock *block = uiLayoutGetBlock(layout);
+
+  if (!prop) {
+    RNA_warning(
+        "Curve Profile property not found: %s.%s", RNA_struct_identifier(ptr->type), propname);
+    return;
+  }
+
+  if (RNA_property_type(prop) != PROP_POINTER) {
+    RNA_warning(
+        "Curve Profile is not a pointer: %s.%s", RNA_struct_identifier(ptr->type), propname);
+    return;
+  }
+
+  cptr = RNA_property_pointer_get(ptr, prop);
+  if (!cptr.data || !RNA_struct_is_a(cptr.type, &RNA_CurveProfile)) {
+    return;
+  }
+
+  /* Share update functionality with the CurveMapping widget template. */
+  cb = MEM_callocN(sizeof(RNAUpdateCb), "RNAUpdateCb");
+  cb->ptr = *ptr;
+  cb->prop = prop;
+
+  id = cptr.owner_id;
+  UI_block_lock_set(block, (id && ID_IS_LINKED(id)), ERROR_LIBDATA_MESSAGE);
+
+  CurveProfile_buttons_layout(layout, &cptr, cb);
+
+  UI_block_lock_clear(block);
+
+  MEM_freeN(cb);
+}
+
+/** \} */
+
 /* -------------------------------------------------------------------- */
 /** \name ColorPicker Template
  * \{ */
index 4cef9ace66ed9bcf56d9d2af61a659668b63f03d..7f7352517c880cd828676ce2fd243b9de47734b2 100644 (file)
@@ -4774,6 +4774,10 @@ void ui_draw_but(const bContext *C, ARegion *ar, uiStyle *style, uiBut *but, rct
         ui_draw_but_CURVE(ar, but, &tui->wcol_regular, rect);
         break;
 
+      case UI_BTYPE_CURVEPROFILE:
+        ui_draw_but_CURVEPROFILE(ar, but, &tui->wcol_regular, rect);
+        break;
+
       case UI_BTYPE_PROGRESS_BAR:
         wt = widget_type(UI_WTYPE_PROGRESSBAR);
         fstyle = &style->widgetlabel;
index 7afd72f33c92e310ea0e31762537c5013f1bb2bb..acdf667b41011acde534768f6f204ef656b87238 100644 (file)
 #include "BKE_unit.h"
 #include "BKE_layer.h"
 #include "BKE_mesh.h"
+#include "BKE_curveprofile.h"
 
 #include "DNA_mesh_types.h"
+#include "DNA_curveprofile_types.h"
 
 #include "RNA_define.h"
 #include "RNA_access.h"
@@ -43,6 +45,7 @@
 #include "WM_types.h"
 
 #include "UI_interface.h"
+#include "UI_resources.h"
 
 #include "ED_mesh.h"
 #include "ED_numinput.h"
@@ -96,6 +99,8 @@ typedef struct {
   short gizmo_flag;
   short value_mode; /* Which value does mouse movement and numeric input affect? */
   float segments;   /* Segments as float so smooth mouse pan works in small increments */
+
+  CurveProfile *custom_profile;
 } BevelData;
 
 enum {
@@ -114,6 +119,8 @@ enum {
   BEV_MODAL_MARK_SHARP_TOGGLE,
   BEV_MODAL_OUTER_MITER_CHANGE,
   BEV_MODAL_INNER_MITER_CHANGE,
+  BEV_MODAL_CUSTOM_PROFILE_TOGGLE,
+  BEV_MODAL_VERTEX_MESH_CHANGE,
 };
 
 static float get_bevel_offset(wmOperator *op)
@@ -129,15 +136,15 @@ static float get_bevel_offset(wmOperator *op)
   return val;
 }
 
-static void edbm_bevel_update_header(bContext *C, wmOperator *op)
+static void edbm_bevel_update_status_text(bContext *C, wmOperator *op)
 {
-  char header[UI_MAX_DRAW_STR];
+  char status_text[UI_MAX_DRAW_STR];
   char buf[UI_MAX_DRAW_STR];
   char *p = buf;
   int available_len = sizeof(buf);
   Scene *sce = CTX_data_scene(C);
   char offset_str[NUM_STR_REP_LEN];
-  const char *mode_str, *omiter_str, *imiter_str;
+  const char *mode_str, *omiter_str, *imiter_str, *vmesh_str;
   PropertyRNA *prop;
 
 #define WM_MODALKEY(_id) \
@@ -167,22 +174,27 @@ static void edbm_bevel_update_header(bContext *C, wmOperator *op)
   prop = RNA_struct_find_property(op->ptr, "miter_inner");
   RNA_property_enum_name_gettexted(
       C, op->ptr, prop, RNA_property_enum_get(op->ptr, prop), &imiter_str);
-
-  BLI_snprintf(header,
-               sizeof(header),
-               TIP_("%s: confirm, "
-                    "%s: cancel, "
-                    "%s: mode (%s), "
-                    "%s: width (%s), "
-                    "%s: segments (%d), "
-                    "%s: profile (%.3f), "
-                    "%s: clamp overlap (%s), "
-                    "%s: vertex only (%s), "
-                    "%s: outer miter (%s), "
-                    "%s: inner miter (%s), "
-                    "%s: harden normals (%s), "
-                    "%s: mark seam (%s), "
-                    "%s: mark sharp (%s)"),
+  prop = RNA_struct_find_property(op->ptr, "vmesh_method");
+  RNA_property_enum_name_gettexted(
+      C, op->ptr, prop, RNA_property_enum_get(op->ptr, prop), &vmesh_str);
+
+  BLI_snprintf(status_text,
+               sizeof(status_text),
+               TIP_("%s: Confirm, "
+                    "%s: Cancel, "
+                    "%s: Mode (%s), "
+                    "%s: Width (%s), "
+                    "%s: Segments (%d), "
+                    "%s: Profile (%.3f), "
+                    "%s: Clamp Overlap (%s), "
+                    "%s: Vertex Only (%s), "
+                    "%s: Outer Miter (%s), "
+                    "%s: Inner Miter (%s), "
+                    "%s: Harden Normals (%s), "
+                    "%s: Mark Seam (%s), "
+                    "%s: Mark Sharp (%s), "
+                    "%s: Custom Profile (%s), "
+                    "%s: Intersection (%s)"),
                WM_MODALKEY(BEV_MODAL_CONFIRM),
                WM_MODALKEY(BEV_MODAL_CANCEL),
                WM_MODALKEY(BEV_MODAL_OFFSET_MODE_CHANGE),
@@ -206,16 +218,21 @@ static void edbm_bevel_update_header(bContext *C, wmOperator *op)
                WM_MODALKEY(BEV_MODAL_MARK_SEAM_TOGGLE),
                WM_bool_as_string(RNA_boolean_get(op->ptr, "mark_seam")),
                WM_MODALKEY(BEV_MODAL_MARK_SHARP_TOGGLE),
-               WM_bool_as_string(RNA_boolean_get(op->ptr, "mark_sharp")));
+               WM_bool_as_string(RNA_boolean_get(op->ptr, "mark_sharp")),
+               WM_MODALKEY(BEV_MODAL_CUSTOM_PROFILE_TOGGLE),
+               WM_bool_as_string(RNA_boolean_get(op->ptr, "use_custom_profile")),
+               WM_MODALKEY(BEV_MODAL_VERTEX_MESH_CHANGE),
+               vmesh_str);
 
 #undef WM_MODALKEY
 
-  ED_workspace_status_text(C, header);
+  ED_workspace_status_text(C, status_text);
 }
 
 static bool edbm_bevel_init(bContext *C, wmOperator *op, const bool is_modal)
 {
   Scene *scene = CTX_data_scene(C);
+  ToolSettings *ts = CTX_data_tool_settings(C);
   BevelData *opdata;
   ViewLayer *view_layer = CTX_data_view_layer(C);
   float pixels_per_inch;
@@ -230,6 +247,9 @@ static bool edbm_bevel_init(bContext *C, wmOperator *op, const bool is_modal)
   uint objects_used_len = 0;
   opdata->max_obj_scale = FLT_MIN;
 
+  /* Put the Curve Profile from the toolsettings into the opdata struct */
+  opdata->custom_profile = ts->custom_bevel_profile_preset;
+
   {
     uint ob_store_len = 0;
     Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(
@@ -318,6 +338,8 @@ static bool edbm_bevel_calc(wmOperator *op)
   const int miter_outer = RNA_enum_get(op->ptr, "miter_outer");
   const int miter_inner = RNA_enum_get(op->ptr, "miter_inner");
   const float spread = RNA_float_get(op->ptr, "spread");
+  const bool use_custom_profile = RNA_boolean_get(op->ptr, "use_custom_profile");
+  const int vmesh_method = RNA_enum_get(op->ptr, "vmesh_method");
 
   for (uint ob_index = 0; ob_index < opdata->ob_store_len; ob_index++) {
     em = opdata->ob_store[ob_index].em;
@@ -344,7 +366,8 @@ static bool edbm_bevel_calc(wmOperator *op)
                  "bevel geom=%hev offset=%f segments=%i vertex_only=%b offset_type=%i profile=%f "
                  "clamp_overlap=%b material=%i loop_slide=%b mark_seam=%b mark_sharp=%b "
                  "harden_normals=%b face_strength_mode=%i "
-                 "miter_outer=%i miter_inner=%i spread=%f smoothresh=%f",
+                 "miter_outer=%i miter_inner=%i spread=%f smoothresh=%f use_custom_profile=%b "
+                 "custom_profile=%p vmesh_method=%i",
                  BM_ELEM_SELECT,
                  offset,
                  segments,
@@ -361,7 +384,10 @@ static bool edbm_bevel_calc(wmOperator *op)
                  miter_outer,
                  miter_inner,
                  spread,
-                 me->smoothresh);
+                 me->smoothresh,
+                 use_custom_profile,
+                 opdata->custom_profile,
+                 vmesh_method);
 
     BMO_op_exec(em->bm, &bmop);
 
@@ -389,7 +415,6 @@ static bool edbm_bevel_calc(wmOperator *op)
 static void edbm_bevel_exit(bContext *C, wmOperator *op)
 {
   BevelData *opdata = op->customdata;
-
   ScrArea *sa = CTX_wm_area(C);
 
   if (sa) {
@@ -430,7 +455,7 @@ static void edbm_bevel_cancel(bContext *C, wmOperator *op)
   ED_region_tag_redraw(CTX_wm_region(C));
 }
 
-/* bevel! yay!!*/
+/* bevel! yay!! */
 static int edbm_bevel_exec(bContext *C, wmOperator *op)
 {
   if (!edbm_bevel_init(C, op, false)) {
@@ -500,7 +525,7 @@ static int edbm_bevel_invoke(bContext *C, wmOperator *op, const wmEvent *event)
 
   edbm_bevel_calc_initial_length(op, event, false);
 
-  edbm_bevel_update_header(C, op);
+  edbm_bevel_update_status_text(C, op);
 
   if (!edbm_bevel_calc(op)) {
     edbm_bevel_cancel(C, op);
@@ -598,13 +623,9 @@ wmKeyMap *bevel_modal_keymap(wmKeyConfig *keyconf)
   static const EnumPropertyItem modal_items[] = {
       {BEV_MODAL_CANCEL, "CANCEL", 0, "Cancel", "Cancel bevel"},
       {BEV_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", "Confirm bevel"},
-      {BEV_MODAL_VALUE_OFFSET, "VALUE_OFFSET", 0, "Value is offset", "Value changes offset"},
-      {BEV_MODAL_VALUE_PROFILE, "VALUE_PROFILE", 0, "Value is profile", "Value changes profile"},
-      {BEV_MODAL_VALUE_SEGMENTS,
-       "VALUE_SEGMENTS",
-       0,
-       "Value is segments",
-       "Value changes segments"},
+      {BEV_MODAL_VALUE_OFFSET, "VALUE_OFFSET", 0, "Change offset", "Value changes offset"},
+      {BEV_MODAL_VALUE_PROFILE, "VALUE_PROFILE", 0, "Change profile", "Value changes profile"},
+      {BEV_MODAL_VALUE_SEGMENTS, "VALUE_SEGMENTS", 0, "Change segments", "Value changes segments"},
       {BEV_MODAL_SEGMENTS_UP, "SEGMENTS_UP", 0, "Increase segments", "Increase segments"},
       {BEV_MODAL_SEGMENTS_DOWN, "SEGMENTS_DOWN", 0, "Decrease segments", "Decrease segments"},
       {BEV_MODAL_OFFSET_MODE_CHANGE,
@@ -647,12 +668,18 @@ wmKeyMap *bevel_modal_keymap(wmKeyConfig *keyconf)
        0,
        "Change inner miter",
        "Cycle through inner miter kinds"},
+      {BEV_MODAL_CUSTOM_PROFILE_TOGGLE, "CUSTOM_PROFILE_TOGGLE", 0, "Toggle custom profile", ""},
+      {BEV_MODAL_VERTEX_MESH_CHANGE,
+       "VERTEX_MESH_CHANGE",
+       0,
+       "Change intersection method",
+       "Cycle through intersection methods"},
       {0, NULL, 0, NULL, NULL},
   };
 
   wmKeyMap *keymap = WM_modalkeymap_get(keyconf, "Bevel Modal Map");
 
-  /* this function is called for each spacetype, only needs to add map once */
+  /* This function is called for each spacetype, only needs to add map once */
   if (keymap && keymap->modal_items) {
     return NULL;
   }
@@ -682,14 +709,14 @@ static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event)
       handleNumInput(C, &opdata->num_input[opdata->value_mode], event)) {
     edbm_bevel_numinput_set_value(op);
     edbm_bevel_calc(op);
-    edbm_bevel_update_header(C, op);
+    edbm_bevel_update_status_text(C, op);
     return OPERATOR_RUNNING_MODAL;
   }
   else if (etype == MOUSEMOVE) {
     if (!has_numinput) {
       edbm_bevel_mouse_set_value(op, event);
       edbm_bevel_calc(op);
-      edbm_bevel_update_header(C, op);
+      edbm_bevel_update_status_text(C, op);
       handled = true;
     }
   }
@@ -703,7 +730,7 @@ static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event)
     }
     RNA_int_set(op->ptr, "segments", (int)opdata->segments);
     edbm_bevel_calc(op);
-    edbm_bevel_update_header(C, op);
+    edbm_bevel_update_status_text(C, op);
     handled = true;
   }
   else if (etype == EVT_MODAL_MAP) {
@@ -723,7 +750,7 @@ static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event)
         opdata->segments = opdata->segments + 1;
         RNA_int_set(op->ptr, "segments", (int)opdata->segments);
         edbm_bevel_calc(op);
-        edbm_bevel_update_header(C, op);
+        edbm_bevel_update_status_text(C, op);
         handled = true;
         break;
 
@@ -731,7 +758,7 @@ static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event)
         opdata->segments = max_ff(opdata->segments - 1, 1);
         RNA_int_set(op->ptr, "segments", (int)opdata->segments);
         edbm_bevel_calc(op);
-        edbm_bevel_update_header(C, op);
+        edbm_bevel_update_status_text(C, op);
         handled = true;
         break;
 
@@ -758,7 +785,7 @@ static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event)
           edbm_bevel_mouse_set_value(op, event);
         }
         edbm_bevel_calc(op);
-        edbm_bevel_update_header(C, op);
+        edbm_bevel_update_status_text(C, op);
         handled = true;
         break;
 
@@ -766,7 +793,7 @@ static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event)
         bool clamp_overlap = RNA_boolean_get(op->ptr, "clamp_overlap");
         RNA_boolean_set(op->ptr, "clamp_overlap", !clamp_overlap);
         edbm_bevel_calc(op);
-        edbm_bevel_update_header(C, op);
+        edbm_bevel_update_status_text(C, op);
         handled = true;
         break;
       }
@@ -790,7 +817,7 @@ static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event)
         bool vertex_only = RNA_boolean_get(op->ptr, "vertex_only");
         RNA_boolean_set(op->ptr, "vertex_only", !vertex_only);
         edbm_bevel_calc(op);
-        edbm_bevel_update_header(C, op);
+        edbm_bevel_update_status_text(C, op);
         handled = true;
         break;
       }
@@ -799,7 +826,7 @@ static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event)
         bool mark_seam = RNA_boolean_get(op->ptr, "mark_seam");
         RNA_boolean_set(op->ptr, "mark_seam", !mark_seam);
         edbm_bevel_calc(op);
-        edbm_bevel_update_header(C, op);
+        edbm_bevel_update_status_text(C, op);
         handled = true;
         break;
       }
@@ -808,7 +835,7 @@ static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event)
         bool mark_sharp = RNA_boolean_get(op->ptr, "mark_sharp");
         RNA_boolean_set(op->ptr, "mark_sharp", !mark_sharp);
         edbm_bevel_calc(op);
-        edbm_bevel_update_header(C, op);
+        edbm_bevel_update_status_text(C, op);
         handled = true;
         break;
       }
@@ -824,7 +851,7 @@ static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event)
         }
         RNA_enum_set(op->ptr, "miter_inner", miter_inner);
         edbm_bevel_calc(op);
-        edbm_bevel_update_header(C, op);
+        edbm_bevel_update_status_text(C, op);
         handled = true;
         break;
       }
@@ -837,7 +864,7 @@ static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event)
         }
         RNA_enum_set(op->ptr, "miter_outer", miter_outer);
         edbm_bevel_calc(op);
-        edbm_bevel_update_header(C, op);
+        edbm_bevel_update_status_text(C, op);
         handled = true;
         break;
       }
@@ -846,7 +873,29 @@ static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event)
         bool harden_normals = RNA_boolean_get(op->ptr, "harden_normals");
         RNA_boolean_set(op->ptr, "harden_normals", !harden_normals);
         edbm_bevel_calc(op);
-        edbm_bevel_update_header(C, op);
+        edbm_bevel_update_status_text(C, op);
+        handled = true;
+        break;
+      }
+
+      case BEV_MODAL_CUSTOM_PROFILE_TOGGLE: {
+        bool use_custom_profile = RNA_boolean_get(op->ptr, "use_custom_profile");
+        RNA_boolean_set(op->ptr, "use_custom_profile", !use_custom_profile);
+        edbm_bevel_calc(op);
+        edbm_bevel_update_status_text(C, op);
+        handled = true;
+        break;
+      }
+
+      case BEV_MODAL_VERTEX_MESH_CHANGE: {
+        int vmesh_method = RNA_enum_get(op->ptr, "vmesh_method");
+        vmesh_method++;
+        if (vmesh_method > BEVEL_VMESH_CUTOFF) {
+          vmesh_method = BEVEL_VMESH_ADJ;
+        }
+        RNA_enum_set(op->ptr, "vmesh_method", vmesh_method);
+        edbm_bevel_calc(op);
+        edbm_bevel_update_status_text(C, op);
         handled = true;
         break;
       }
@@ -858,13 +907,84 @@ static int edbm_bevel_modal(bContext *C, wmOperator *op, const wmEvent *event)
       handleNumInput(C, &opdata->num_input[opdata->value_mode], event)) {
     edbm_bevel_numinput_set_value(op);
     edbm_bevel_calc(op);
-    edbm_bevel_update_header(C, op);
+    edbm_bevel_update_status_text(C, op);
     return OPERATOR_RUNNING_MODAL;
   }
 
   return OPERATOR_RUNNING_MODAL;
 }
 
+static void edbm_bevel_ui(bContext *C, wmOperator *op)
+{
+  uiLayout *layout = op->layout;
+  uiLayout *row, *col, *split;
+  PointerRNA ptr, toolsettings_ptr;
+  PropertyRNA *prop;
+  const char *offset_name;
+
+  RNA_pointer_create(NULL, op->type->srna, op->properties, &ptr);
+
+  if (RNA_enum_get(&ptr, "offset_type") == BEVEL_AMT_PERCENT) {
+    uiItemR(layout, &ptr, "offset_pct", 0, NULL, ICON_NONE);
+  }
+  else {
+    switch (RNA_enum_get(&ptr, "offset_type")) {
+      case BEVEL_AMT_DEPTH:
+        offset_name = "Depth";
+        break;
+      case BEVEL_AMT_WIDTH:
+        offset_name = "Width";
+        break;
+      case BEVEL_AMT_OFFSET:
+        offset_name = "Offset";
+        break;
+    }
+    prop = RNA_struct_find_property(op->ptr, "offset_type");
+    RNA_property_enum_name_gettexted(
+        C, op->ptr, prop, RNA_property_enum_get(op->ptr, prop), &offset_name);
+    uiItemR(layout, &ptr, "offset", 0, offset_name, ICON_NONE);
+  }
+  row = uiLayoutRow(layout, true);
+  uiItemR(row, &ptr, "offset_type", UI_ITEM_R_EXPAND, NULL, ICON_NONE);
+
+  split = uiLayoutSplit(layout, 0.5f, true);
+  col = uiLayoutColumn(split, true);
+  uiItemR(col, &ptr, "vertex_only", 0, NULL, ICON_NONE);
+  uiItemR(col, &ptr, "clamp_overlap", 0, NULL, ICON_NONE);
+  uiItemR(col, &ptr, "loop_slide", 0, NULL, ICON_NONE);
+  col = uiLayoutColumn(split, true);
+  uiItemR(col, &ptr, "mark_seam", 0, NULL, ICON_NONE);
+  uiItemR(col, &ptr, "mark_sharp", 0, NULL, ICON_NONE);
+  uiItemR(col, &ptr, "harden_normals", 0, NULL, ICON_NONE);
+
+  uiItemR(layout, &ptr, "segments", 0, NULL, ICON_NONE);
+  uiItemR(layout, &ptr, "profile", UI_ITEM_R_SLIDER, NULL, ICON_NONE);
+  uiItemR(layout, &ptr, "material", 0, NULL, ICON_NONE);
+
+  uiItemL(layout, "Miter Type:", ICON_NONE);
+  uiItemR(layout, &ptr, "miter_outer", 0, "Outer", ICON_NONE);
+  uiItemR(layout, &ptr, "miter_inner", 0, "Inner", ICON_NONE);
+  if (RNA_enum_get(&ptr, "miter_inner") == BEVEL_MITER_ARC) {
+    uiItemR(layout, &ptr, "spread", 0, NULL, ICON_NONE);
+  }
+
+  uiItemL(layout, "Face Strength Mode:", ICON_NONE);
+  row = uiLayoutRow(layout, true);
+  uiItemR(row, &ptr, "face_strength_mode", UI_ITEM_R_EXPAND, NULL, ICON_NONE);
+
+  uiItemL(layout, "Intersection Type:", ICON_NONE);
+  row = uiLayoutRow(layout, true);
+  uiItemR(row, &ptr, "vmesh_method", UI_ITEM_R_EXPAND, NULL, ICON_NONE);
+
+  uiItemR(layout, &ptr, "use_custom_profile", 0, NULL, ICON_NONE);
+  if (RNA_boolean_get(&ptr, "use_custom_profile")) {
+    /* Get an RNA pointer to ToolSettings to give to the curve profile template code */
+    Scene *scene = CTX_data_scene(C);
+    RNA_pointer_create(&scene->id, &RNA_ToolSettings, scene->toolsettings, &toolsettings_ptr);
+    uiTemplateCurveProfile(layout, &toolsettings_ptr, "custom_bevel_profile_preset");
+  }
+}
+
 void MESH_OT_bevel(wmOperatorType *ot)
 {
   PropertyRNA *prop;
@@ -906,10 +1026,19 @@ void MESH_OT_bevel(wmOperatorType *ot)
       {0, NULL, 0, NULL, NULL},
   };
 
+  static EnumPropertyItem vmesh_method_items[] = {
+      {BEVEL_VMESH_ADJ, "ADJ", 0, "Grid Fill", "Default patterned fill"},
+      {BEVEL_VMESH_CUTOFF,
+       "CUTOFF",
+       0,
+       "Cutoff",
+       "A cut-off at each profile's end before the intersection"},
+      {0, NULL, 0, NULL, NULL},
+  };
+
   /* identifiers */
   ot->name = "Bevel";
-  ot->description =
-      "Cut into selected items at an angle to create flat or rounded bevel or chamfer";
+  ot->description = "Cut into selected items at an angle to create bevel or chamfer";
   ot->idname = "MESH_OT_bevel";
 
   /* api callbacks */
@@ -919,19 +1048,23 @@ void MESH_OT_bevel(wmOperatorType *ot)
   ot->cancel = edbm_bevel_cancel;
   ot->poll = ED_operator_editmesh;
   ot->poll_property = edbm_bevel_poll_property;
+  ot->ui = edbm_bevel_ui;
 
   /* flags */
   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_GRAB_CURSOR_XY | OPTYPE_BLOCKING;
 
+  /* properties */
   RNA_def_enum(
       ot->srna, "offset_type", offset_type_items, 0, "Width Type", "What distance Width measures");
   prop = RNA_def_property(ot->srna, "offset", PROP_FLOAT, PROP_DISTANCE);
   RNA_def_property_range(prop, 0.0, 1e6);
-  RNA_def_property_ui_range(prop, 0.0f, 100.0, 1, 3);
+  RNA_def_property_ui_range(prop, 0.0, 100.0, 1, 3);
   RNA_def_property_ui_text(prop, "Width", "Bevel amount");
+
   prop = RNA_def_property(ot->srna, "offset_pct", PROP_FLOAT, PROP_PERCENTAGE);
   RNA_def_property_range(prop, 0.0, 100);
   RNA_def_property_ui_text(prop, "Width Percent", "Bevel amount for percentage method");
+
   RNA_def_int(ot->srna,
               "segments",
               1,
@@ -941,6 +1074,7 @@ void MESH_OT_bevel(wmOperatorType *ot)
               "Segments for curved edge",
               1,
               100);
+
   RNA_def_float(ot->srna,
                 "profile",
                 0.5f,
@@ -950,16 +1084,22 @@ void MESH_OT_bevel(wmOperatorType *ot)
                 "Controls profile shape (0.5 = round)",
                 PROFILE_HARD_MIN,
                 1.0f);
+
   RNA_def_boolean(ot->srna, "vertex_only", false, "Vertex Only", "Bevel only vertices");
+
   RNA_def_boolean(ot->srna,
                   "clamp_overlap",
                   false,
                   "Clamp Overlap",
                   "Do not allow beveled edges/vertices to overlap each other");
+
   RNA_def_boolean(
-      ot->srna, "loop_slide", true, "Loop Slide", "Prefer slide along edge to even widths");
+      ot->srna, "loop_slide", true, "Loop Slide", "Prefer sliding along edges to even widths");
+
   RNA_def_boolean(ot->srna, "mark_seam", false, "Mark Seams", "Mark Seams along beveled edges");
+
   RNA_def_boolean(ot->srna, "mark_sharp", false, "Mark Sharp", "Mark beveled edges as sharp");
+
   RNA_def_int(ot->srna,
               "material",
               -1,
@@ -969,29 +1109,34 @@ void MESH_OT_bevel(wmOperatorType *ot)
               "Material for bevel faces (-1 means use adjacent faces)",
               -1,
               100);
+
   RNA_def_boolean(ot->srna,
                   "harden_normals",
                   false,
                   "Harden Normals",
                   "Match normals of new faces to adjacent faces");
+
   RNA_def_enum(ot->srna,
                "face_strength_mode",
                face_strength_mode_items,
                BEVEL_FACE_STRENGTH_NONE,
                "Face Strength Mode",
                "Whether to set face strength, and which faces to set face strength on");
+
   RNA_def_enum(ot->srna,
                "miter_outer",
                miter_outer_items,
                BEVEL_MITER_SHARP,
                "Outer Miter",
                "Pattern to use for outside of miters");
+
   RNA_def_enum(ot->srna,
                "miter_inner",
                miter_inner_items,
                BEVEL_MITER_SHARP,
                "Inner Miter",
                "Pattern to use for inside of miters");
+
   RNA_def_float(ot->srna,
                 "spread",
                 0.1f,
@@ -1001,6 +1146,20 @@ void MESH_OT_bevel(wmOperatorType *ot)
                 "Amount to spread arcs for arc inner miters",
                 0.0f,
                 100.0f);
+
+  RNA_def_boolean(ot->srna,
+                  "use_custom_profile",
+                  false,
+                  "Custom Profile",
+                  "Use a custom profile for the bevel");
+
+  RNA_def_enum(ot->srna,
+               "vmesh_method",
+               vmesh_method_items,
+               BEVEL_VMESH_ADJ,
+               "Vertex Mesh Method",
+               "The method to use to create meshes at intersections");
+
   prop = RNA_def_boolean(ot->srna, "release_confirm", 0, "Confirm on Release", "");
   RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
 }
diff --git a/source/blender/makesdna/DNA_curveprofile_types.h b/source/blender/makesdna/DNA_curveprofile_types.h
new file mode 100644 (file)
index 0000000..d7e3591
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * 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 2
+ * 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, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright (C) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup DNA
+ */
+
+#ifndef DNA_PROFILEPATH_TYPES_H
+#define DNA_PROFILEPATH_TYPES_H
+
+#include "DNA_vec_types.h"
+
+/** Number of points in high resolution table is dynamic up to a maximum. */
+#define PROF_TABLE_MAX 512
+/** Number of table points per control point. */
+#define PROF_RESOL 16
+/** Dynamic size of widget's high resolution table. Input should be profile->totpoint. */
+#define PROF_N_TABLE(n_pts) min_ii(PROF_TABLE_MAX, (((n_pts - 1)) * PROF_RESOL) + 1)
+
+/** Each control point that makes up the profile.
+ * \note The flags use the same enum as Bezier curves, but they aren't garanteed
+ *       to have identical functionality, and all types aren't implemented. */
+typedef struct CurveProfilePoint {
+  /** Location of the point, keep together. */
+  float x, y;
+  /** Flag selection state and others. */
+  short flag;
+  /** Flags for both handle's type (eBezTriple_Handle). */
+  char h1, h2;
+} CurveProfilePoint;
+
+/** #CurveProfilePoint.flag */
+enum {
+  PROF_SELECT = (1 << 0),
+};
+
+/** Defines a profile */
+typedef struct CurveProfile {
+  /** Number of user-added points that define the profile. */
+  short path_len;
+  /** Number of sampled points. */
+  short segments_len;
+  /** Preset to use when reset. */
+  int preset;
+  /** Sequence of points defining the shape of the curve.  */
+  CurveProfilePoint *path;
+  /** Display and evaluation table at higher resolution for curves. */
+  CurveProfilePoint *table;
+  /** The positions of the sampled points. Used to display a preview of where they will be. */
+  CurveProfilePoint *segments;
+  /** Flag for mode states, sampling options, etc... */
+  int flag;
+  /** Used for keeping track how many times the widget is changed. */
+  int changed_timestamp;
+  /** Widget's current view, and clipping rect (is default rect too). */
+  rctf view_rect, clip_rect;
+} CurveProfile;
+
+/** #CurveProfile.flag */
+enum {
+  PROF_USE_CLIP = (1 << 0),              /* Keep control points inside bounding rectangle. */
+  PROF_SYMMETRY_MODE = (1 << 1),         /* Unused for now. */
+  PROF_SAMPLE_STRAIGHT_EDGES = (1 << 2), /* Sample extra points on straight edges. */
+  PROF_SAMPLE_EVEN_LENGTHS = (1 << 3),   /* Put segments evenly spaced along the path. */
+};
+
+typedef enum eCurveProfilePresets {
+  PROF_PRESET_LINE = 0,     /* Default simple line between end points. */
+  PROF_PRESET_SUPPORTS = 1, /* Support loops for a regular curved profile. */
+  PROF_PRESET_CORNICE = 2,  /* Moulding type example. */
+  PROF_PRESET_CROWN = 3,    /* Second moulding example. */
+  PROF_PRESET_STEPS = 4,    /* Dynamic number of steps defined by totsegments. */
+} eCurveProfilePresets;
+
+#endif
index 9c4d7bcd3b1fbf637474797677896d7070a05093..a8db46238d8f09af9c80b3be9df33012f93633fb 100644 (file)
@@ -377,10 +377,11 @@ typedef struct BevelModifierData {
   short mat;
   short edge_flags;
   short face_str_mode;
-  /* patterns to use for mitering non-reflex and reflex miter edges */
+  /** Patterns to use for mitering non-reflex and reflex miter edges */
   short miter_inner;
   short miter_outer;
-  char _pad0[2];
+  /** The method to use for creating >2-way intersections */
+  short vmesh_method;
   /** Controls profile shape (0->1, .5 is round). */
   float profile;
   /** if the MOD_BEVEL_ANGLE is set,
@@ -390,6 +391,10 @@ typedef struct BevelModifierData {
   /** if the MOD_BEVEL_VWEIGHT option is set,
    * this will be the name of the vert group, MAX_VGROUP_NAME */
   char defgrp_name[64];
+
+  /** Curve info for the custom profile */
+  struct CurveProfile *custom_profile;
+
 } BevelModifierData;
 
 /* BevelModifierData->flags and BevelModifierData->lim_flags */
@@ -399,8 +404,8 @@ enum {
   MOD_BEVEL_ANGLE = (1 << 3),
   MOD_BEVEL_WEIGHT = (1 << 4),
   MOD_BEVEL_VGROUP = (1 << 5),
-  /*  unused                  = (1 << 7), */
-  /*  unused                  = (1 << 8), */
+  MOD_BEVEL_CUSTOM_PROFILE = (1 << 7),
+  MOD_BEVEL_SAMPLE_STRAIGHT = (1 << 8),
   /*  unused                  = (1 << 9), */
   /*  unused                  = (1 << 10), */
   /*  unused                  = (1 << 11), */
@@ -439,6 +444,12 @@ enum {
   MOD_BEVEL_MITER_ARC,
 };
 
+/* BevelModifier->vmesh_method */
+enum {
+  MOD_BEVEL_VMESH_ADJ,
+  MOD_BEVEL_VMESH_CUTOFF,
+};
+
 typedef struct SmokeModifierData {
   ModifierData modifier;
 
index 2ca9e3b2ef731022d14734e236332efd76196326..e60b16453b20b455e730d730ece21121da02323d 100644 (file)
@@ -33,7 +33,8 @@
 extern "C" {
 #endif
 
-#include "DNA_color_types.h"      /* color management */
+#include "DNA_color_types.h" /* color management */
+#include "DNA_curveprofile_types.h"
 #include "DNA_customdata_types.h" /* Scene's runtime cddata masks. */
 #include "DNA_vec_types.h"
 #include "DNA_listBase.h"
@@ -50,6 +51,7 @@ struct Brush;
 struct Collection;
 struct ColorSpace;
 struct CurveMapping;
+struct CurveProfile;
 struct CustomData_MeshMasks;
 struct Editing;
 struct Image;
@@ -1515,6 +1517,11 @@ typedef struct ToolSettings {
   /* Normal Editing */
   float normal_vector[3];
   char _pad6[4];
+
+  /* Custom Curve Profile for bevel tool:
+   * Temporary until there is a proper preset system that stores the profiles or maybe stores
+   * entire bevel configurations. */
+  struct CurveProfile *custom_bevel_profile_preset;
 } ToolSettings;
 
 /* *************************************************************** */
index 80d37d89f1491667e8c87ebb5753e4034f89edcc..d6f52e48ebea73d3a15f760f29013e6434b532ae 100644 (file)
@@ -131,6 +131,7 @@ static const char *includefiles[] = {
     "DNA_layer_types.h",
     "DNA_workspace_types.h",
     "DNA_lightprobe_types.h",
+    "DNA_curveprofile_types.h",
 
     /* see comment above before editing! */
 
@@ -1590,6 +1591,7 @@ int main(int argc, char **argv)
 #include "DNA_layer_types.h"
 #include "DNA_workspace_types.h"
 #include "DNA_lightprobe_types.h"
+#include "DNA_curveprofile_types.h"
 
 /* end of list */
 
index a1369d84786e94589eb313dc611999e0bdd175c3..5bf6fb40c6a827b4be949da04bb1ce148c5ad6b2 100644 (file)
@@ -209,6 +209,8 @@ extern StructRNA RNA_CurveMapPoint;
 extern StructRNA RNA_CurveMapping;
 extern StructRNA RNA_CurveModifier;
 extern StructRNA RNA_CurvePoint;
+extern StructRNA RNA_CurveProfile;
+extern StructRNA RNA_CurveProfilePoint;
 extern StructRNA RNA_DampedTrackConstraint;
 extern StructRNA RNA_DataTransferModifier;
 extern StructRNA RNA_DecimateModifier;
index 458f031ceae30c7634739a401b523bc0caf6a3f0..9290b81f1af070509710c75aa426af35ab6efdbb 100644 (file)
@@ -216,6 +216,8 @@ extern const EnumPropertyItem rna_enum_dt_layers_select_dst_items[];
 extern const EnumPropertyItem rna_enum_abc_compression_items[];
 extern const EnumPropertyItem rna_enum_context_mode_items[];
 
+extern const EnumPropertyItem rna_enum_curveprofile_preset_items[];
+
 /* API calls */
 int rna_node_tree_type_to_enum(struct bNodeTreeType *typeinfo);
 int rna_node_tree_idname_to_enum(const char *idname);
index 4bb53404724552b7c9ac83efde3a777f7f52d533..7c06582dd6ac66dd02584a930cd80cccf333cb72 100644 (file)
@@ -68,6 +68,7 @@ set(DEFSRC
   rna_palette.c
   rna_particle.c
   rna_pose.c
+  rna_curveprofile.c
   rna_render.c
   rna_rigidbody.c
   rna_rna.c
index 2d4da94261045e2563cef6e063a0d6d95a46c04a..009a723551e3b20f29f3bcb1a52e0afa76648318 100644 (file)
@@ -4255,6 +4255,7 @@ static RNAProcessItem PROCESS_ITEMS[] = {
     {"rna_palette.c", NULL, RNA_def_palette},
     {"rna_particle.c", NULL, RNA_def_particle},
     {"rna_pose.c", "rna_pose_api.c", RNA_def_pose},
+    {"rna_curveprofile.c", NULL, RNA_def_profile},
     {"rna_lightprobe.c", NULL, RNA_def_lightprobe},
     {"rna_render.c", NULL, RNA_def_render},
     {"rna_rigidbody.c", NULL, RNA_def_rigidbody},
diff --git a/source/blender/makesrna/intern/rna_curveprofile.c b/source/blender/makesrna/intern/rna_curveprofile.c
new file mode 100644 (file)
index 0000000..71e1aac
--- /dev/null
@@ -0,0 +1,322 @@
+/*
+ * 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 2
+ * 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, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup RNA
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "DNA_curveprofile_types.h"
+#include "DNA_curve_types.h"
+#include "DNA_texture_types.h"
+
+#include "BLI_utildefines.h"
+
+#include "RNA_define.h"
+#include "rna_internal.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#ifdef RNA_RUNTIME
+
+#  include "RNA_access.h"
+
+#  include "DNA_image_types.h"
+#  include "DNA_material_types.h"
+#  include "DNA_movieclip_types.h"
+#  include "DNA_node_types.h"
+#  include "DNA_object_types.h"
+#  include "DNA_particle_types.h"
+#  include "DNA_sequence_types.h"
+
+#  include "MEM_guardedalloc.h"
+
+#  include "BKE_colorband.h"
+#  include "BKE_curveprofile.h"
+#  include "BKE_image.h"
+#  include "BKE_movieclip.h"
+#  include "BKE_node.h"
+#  include "BKE_sequencer.h"
+#  include "BKE_linestyle.h"
+
+#  include "DEG_depsgraph.h"
+
+#  include "ED_node.h"
+
+#  include "IMB_colormanagement.h"
+#  include "IMB_imbuf.h"
+
+static void rna_CurveProfile_clip_set(PointerRNA *ptr, bool value)
+{
+  CurveProfile *profile = (CurveProfile *)ptr->data;
+
+  if (value) {
+    profile->flag |= PROF_USE_CLIP;
+  }
+  else {
+    profile->flag &= ~PROF_USE_CLIP;
+  }
+
+  BKE_curveprofile_update(profile, false);
+}
+
+static void rna_CurveProfile_sample_straight_set(PointerRNA *ptr, bool value)
+{
+  CurveProfile *profile = (CurveProfile *)ptr->data;
+
+  if (value) {
+    profile->flag |= PROF_SAMPLE_STRAIGHT_EDGES;
+  }
+  else {
+    profile->flag &= ~PROF_SAMPLE_STRAIGHT_EDGES;
+  }
+
+  BKE_curveprofile_update(profile, false);
+}
+
+static void rna_CurveProfile_sample_even_set(PointerRNA *ptr, bool value)
+{
+  CurveProfile *profile = (CurveProfile *)ptr->data;
+
+  if (value) {
+    profile->flag |= PROF_SAMPLE_EVEN_LENGTHS;
+  }
+  else {
+    profile->flag &= ~PROF_SAMPLE_EVEN_LENGTHS;
+  }
+
+  BKE_curveprofile_update(profile, false);
+}
+
+static void rna_CurveProfile_remove_point(CurveProfile *profile,
+                                          ReportList *reports,
+                                          PointerRNA *point_ptr)
+{
+  CurveProfilePoint *point = point_ptr->data;
+  if (BKE_curveprofile_remove_point(profile, point) == false) {
+    BKE_report(reports, RPT_ERROR, "Unable to remove path point");
+    return;
+  }
+
+  RNA_POINTER_INVALIDATE(point_ptr);
+}
+
+static void rna_CurveProfile_evaluate(struct CurveProfile *profile,
+                                      ReportList *reports,
+                                      float length_portion,
+                                      float *location)
+{
+  if (!profile->table) {
+    BKE_report(reports, RPT_ERROR, "CurveProfile table not initialized, call initialize()");
+  }
+  BKE_curveprofile_evaluate_length_portion(profile, length_portion, &location[0], &location[1]);
+}
+
+static void rna_CurveProfile_initialize(struct CurveProfile *profile, int segments_len)
+{
+  BKE_curveprofile_initialize(profile, (short)segments_len);
+}
+
+static void rna_CurveProfile_update(struct CurveProfile *profile)
+{
+  BKE_curveprofile_update(profile, false);
+}
+
+#else
+
+static const EnumPropertyItem prop_handle_type_items[] = {
+    {HD_AUTO, "AUTO", 0, "Auto Handle", ""},
+    {HD_VECT, "VECTOR", 0, "Vector Handle", ""},
+    {0, NULL, 0, NULL, NULL},
+};
+
+static void rna_def_curveprofilepoint(BlenderRNA *brna)
+{
+  StructRNA *srna;
+  PropertyRNA *prop;
+
+  srna = RNA_def_struct(brna, "CurveProfilePoint", NULL);
+  RNA_def_struct_ui_text(srna, "CurveProfilePoint", "Point of a path used to define a profile");
+
+  prop = RNA_def_property(srna, "location", PROP_FLOAT, PROP_XYZ);
+  RNA_def_property_float_sdna(prop, NULL, "x");
+  RNA_def_property_array(prop, 2);
+  RNA_def_property_ui_text(prop, "Location", "X/Y coordinates of the path point");
+
+  prop = RNA_def_property(srna, "handle_type_1", PROP_ENUM, PROP_NONE);
+  RNA_def_property_enum_sdna(prop, NULL, "h1");
+  RNA_def_property_enum_items(prop, prop_handle_type_items);
+  RNA_def_property_ui_text(
+      prop, "First Handle Type", "Path interpolation at this point: Bezier or vector");
+
+  prop = RNA_def_property(srna, "handle_type_2", PROP_ENUM, PROP_NONE);
+  RNA_def_property_enum_sdna(prop, NULL, "h2");
+  RNA_def_property_enum_items(prop, prop_handle_type_items);
+  RNA_def_property_ui_text(
+      prop, "Second Handle Type", "Path interpolation at this point: Bezier or vector");
+
+  prop = RNA_def_property(srna, "select", PROP_BOOLEAN, PROP_NONE);
+  RNA_def_property_boolean_sdna(prop, NULL, "flag", PROF_SELECT);
+  RNA_def_property_ui_text(prop, "Select", "Selection state of the path point");
+}
+
+static void rna_def_curveprofile_points_api(BlenderRNA *brna, PropertyRNA *cprop)
+{
+  StructRNA *srna;
+  PropertyRNA *parm;
+  FunctionRNA *func;
+
+  RNA_def_property_srna(cprop, "CurveProfilePoints");
+  srna = RNA_def_struct(brna, "CurveProfilePoints", NULL);
+  RNA_def_struct_sdna(srna, "CurveProfile");
+  RNA_def_struct_ui_text(srna, "Profile Point", "Collection of Profile Points");
+
+  func = RNA_def_function(srna, "add", "BKE_curveprofile_insert");
+  RNA_def_function_ui_description(func, "Add point to the profile");
+  parm = RNA_def_float(func,
+                       "x",
+                       0.0f,
+                       -FLT_MAX,
+                       FLT_MAX,
+                       "X Position",
+                       "X Position for new point",
+                       -FLT_MAX,
+                       FLT_MAX);
+  RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
+  parm = RNA_def_float(func,
+                       "y",
+                       0.0f,
+                       -FLT_MAX,
+                       FLT_MAX,
+                       "Y Position",
+                       "Y Position for new point",
+                       -FLT_MAX,
+                       FLT_MAX);
+  RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
+  parm = RNA_def_pointer(func, "point", "CurveProfilePoint", "", "New point");
+  RNA_def_function_return(func, parm);
+
+  func = RNA_def_function(srna, "remove", "rna_CurveProfile_remove_point");
+  RNA_def_function_flag(func, FUNC_USE_REPORTS);
+  RNA_def_function_ui_description(func, "Delete point from the profile");
+  parm = RNA_def_pointer(func, "point", "CurveProfilePoint", "", "Point to remove");
+  RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR);
+  RNA_def_parameter_clear_flags(parm, PROP_THICK_WRAP, 0);
+}
+
+static void rna_def_curveprofile(BlenderRNA *brna)
+{
+  StructRNA *srna;
+  PropertyRNA *prop;
+  PropertyRNA *parm;
+  FunctionRNA *func;
+
+  static const EnumPropertyItem rna_enum_curveprofile_preset_items[] = {
+      {PROF_PRESET_LINE, "LINE", 0, "Line", "Default"},
+      {PROF_PRESET_SUPPORTS, "SUPPORTS", 0, "Support Loops", "Loops on each side of the profile"},
+      {PROF_PRESET_CORNICE, "CORNICE", 0, "Cornice Moulding", ""},
+      {PROF_PRESET_CROWN, "CROWN", 0, "Crown Moulding", ""},
+      {PROF_PRESET_STEPS, "STEPS", 0, "Steps", "A number of steps defined by the segments"},
+      {0, NULL, 0, NULL, NULL},
+  };
+
+  srna = RNA_def_struct(brna, "CurveProfile", NULL);
+  RNA_def_struct_ui_text(srna, "CurveProfile", "Profile Path editor used to build a profile path");
+
+  prop = RNA_def_property(srna, "preset", PROP_ENUM, PROP_NONE);
+  RNA_def_property_enum_sdna(prop, NULL, "preset");
+  RNA_def_property_enum_items(prop, rna_enum_curveprofile_preset_items);
+  RNA_def_property_ui_text(prop, "Preset", "");
+
+  prop = RNA_def_property(srna, "use_clip", PROP_BOOLEAN, PROP_NONE);
+  RNA_def_property_boolean_sdna(prop, NULL, "flag", PROF_USE_CLIP);
+  RNA_def_property_ui_text(prop, "Clip", "Force the path view to fit a defined boundary");
+  RNA_def_property_boolean_funcs(prop, NULL, "rna_CurveProfile_clip_set");
+
+  prop = RNA_def_property(srna, "use_sample_straight_edges", PROP_BOOLEAN, PROP_NONE);
+  RNA_def_property_boolean_sdna(prop, NULL, "flag", PROF_SAMPLE_STRAIGHT_EDGES);
+  RNA_def_property_ui_text(prop, "Sample Straight Edges", "Sample edges with vector handles");
+  RNA_def_property_boolean_funcs(prop, NULL, "rna_CurveProfile_sample_straight_set");
+
+  prop = RNA_def_property(srna, "use_sample_even_lengths", PROP_BOOLEAN, PROP_NONE);
+  RNA_def_property_boolean_sdna(prop, NULL, "flag", PROF_SAMPLE_EVEN_LENGTHS);
+  RNA_def_property_ui_text(prop, "Sample Even Lengths", "Sample edges with even lengths");
+  RNA_def_property_boolean_funcs(prop, NULL, "rna_CurveProfile_sample_even_set");
+
+  func = RNA_def_function(srna, "update", "rna_CurveProfile_update");
+  RNA_def_function_ui_description(func, "Update the profile");
+
+  func = RNA_def_function(srna, "initialize", "rna_CurveProfile_initialize");
+  parm = RNA_def_int(func,
+                     "totsegments",
+                     1,
+                     1,
+                     1000,
+                     "",
+                     "The number of segment values to"
+                     " initialize the segments table with",
+                     1,
+                     100);
+  RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED);
+  RNA_def_function_ui_description(func, "Set the number of display segments and fill tables");
+
+  prop = RNA_def_property(srna, "points", PROP_COLLECTION, PROP_NONE);
+  RNA_def_property_collection_sdna(prop, NULL, "path", "path_len");
+  RNA_def_property_struct_type(prop, "CurveProfilePoint");
+  RNA_def_property_ui_text(prop, "Points", "Profile control points");
+  rna_def_curveprofile_points_api(brna, prop);
+
+  prop = RNA_def_property(srna, "segments", PROP_COLLECTION, PROP_NONE);
+  RNA_def_property_collection_sdna(prop, NULL, "segments", "segments_len");
+  RNA_def_property_struct_type(prop, "CurveProfilePoint");
+  RNA_def_property_ui_text(prop, "Segments", "Segments sampled from control points");
+
+  func = RNA_def_function(srna, "evaluate", "rna_CurveProfile_evaluate");
+  RNA_def_function_flag(func, FUNC_USE_REPORTS);
+  RNA_def_function_ui_description(func, "Evaluate the at the given portion of the path length");
+  parm = RNA_def_float(func,
+                       "length_portion",
+                       0.0f,
+                       0.0f,
+                       1.0f,
+                       "Length Portion",
+                       "Portion of the path length to travel before evaluation",
+                       0.0f,
+                       1.0f);
+  RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
+  parm = RNA_def_float_vector(func,
+                              "location",
+                              2,
+                              NULL,
+                              -100.0f,
+                              100.0f,
+                              "Location",
+                              "The location at the given portion of the profile",
+                              -100.0f,
+                              100.0f);
+  RNA_def_function_output(func, parm);
+}
+
+void RNA_def_profile(BlenderRNA *brna)
+{
+  rna_def_curveprofilepoint(brna);
+  rna_def_curveprofile(brna);
+}
+
+#endif
index 04a59a48b63bbf17788447229a8d460f3bdf6371..94394a4826bcd958d2c48580bd5f72c27d4fac6c 100644 (file)
@@ -178,6 +178,7 @@ void RNA_def_packedfile(struct BlenderRNA *brna);
 void RNA_def_palette(struct BlenderRNA *brna);
 void RNA_def_particle(struct BlenderRNA *brna);
 void RNA_def_pose(struct BlenderRNA *brna);
+void RNA_def_profile(struct BlenderRNA *brna);
 void RNA_def_lightprobe(struct BlenderRNA *brna);
 void RNA_def_render(struct BlenderRNA *brna);
 void RNA_def_rigidbody(struct BlenderRNA *brna);
index 6e81b1343f5ffc3d6adebfa926790e6509d36dd6..789946d32684892b9344009dd3358e78cee42c02 100644 (file)
@@ -37,6 +37,7 @@
 #include "BLT_translation.h"
 
 #include "BKE_animsys.h"
+#include "BKE_curveprofile.h"
 #include "BKE_data_transfer.h"
 #include "BKE_dynamicpaint.h"
 #include "BKE_effect.h"
@@ -433,7 +434,6 @@ const EnumPropertyItem rna_enum_axis_flag_xyz_items[] = {
 };
 
 #ifdef RNA_RUNTIME
-
 #  include "DNA_particle_types.h"
 #  include "DNA_curve_types.h"
 #  include "DNA_smoke_types.h"
@@ -998,6 +998,18 @@ static PointerRNA rna_CollisionModifier_settings_get(PointerRNA *ptr)
   return rna_pointer_inherit_refine(ptr, &RNA_CollisionSettings, ob->pd);
 }
 
+/* Special update function for setting the number of segments of the modifier that also resamples
+ * the segments in the custom profile. */
+static void rna_BevelModifier_update_segments(Main *bmain, Scene *scene, PointerRNA *ptr)
+{
+  BevelModifierData *bmd = (BevelModifierData *)ptr->data;
+  if (RNA_boolean_get(ptr, "use_custom_profile")) {
+    short segments = (short)RNA_int_get(ptr, "segments");
+    BKE_curveprofile_initialize(bmd->custom_profile, segments);
+  }
+  rna_Modifier_update(bmain, scene, ptr);
+}
+
 static void rna_UVProjectModifier_num_projectors_set(PointerRNA *ptr, int value)
 {
   UVProjectModifierData *md = (UVProjectModifierData *)ptr->data;
@@ -3583,10 +3595,26 @@ static void rna_def_modifier_bevel(BlenderRNA *brna)
       {0, NULL, 0, NULL, NULL},
   };
 
-  static EnumPropertyItem prop_miter_items[] = {
-      {MOD_BEVEL_MITER_SHARP, "MITER_SHARP", 0, "Sharp", "Default sharp miter"},
-      {MOD_BEVEL_MITER_PATCH, "MITER_PATCH", 0, "Patch", "Miter with extra corner"},
-      {MOD_BEVEL_MITER_ARC, "MITER_ARC", 0, "Arc", "Miter with curved arc"},
+  static const EnumPropertyItem prop_miter_outer_items[] = {
+      {MOD_BEVEL_MITER_SHARP, "MITER_SHARP", 0, "Sharp", "Outside of miter is sharp"},
+      {MOD_BEVEL_MITER_PATCH, "MITER_PATCH", 0, "Patch", "Outside of miter is squared-off patch"},
+      {MOD_BEVEL_MITER_ARC, "MITER_ARC", 0, "Arc", "Outside of miter is arc"},
+      {0, NULL, 0, NULL, NULL},
+  };
+
+  static const EnumPropertyItem prop_miter_inner_items[] = {
+      {MOD_BEVEL_MITER_SHARP, "MITER_SHARP", 0, "Sharp", "Inside of miter is sharp"},
+      {MOD_BEVEL_MITER_ARC, "MITER_ARC", 0, "Arc", "Inside of miter is arc"},
+      {0, NULL, 0, NULL, NULL},
+  };
+
+  static EnumPropertyItem prop_vmesh_method_items[] = {
+      {MOD_BEVEL_VMESH_ADJ, "ADJ", 0, "Grid Fill", "Default patterned fill"},
+      {MOD_BEVEL_VMESH_CUTOFF,
+       "CUTOFF",
+       0,
+       "Cutoff",
+       "A cut-off at the end of each profile before the intersection"},
       {0, NULL, 0, NULL, NULL},
   };
 
@@ -3614,7 +3642,7 @@ static void rna_def_modifier_bevel(BlenderRNA *brna)
   RNA_def_property_int_sdna(prop, NULL, "res");
   RNA_def_property_range(prop, 1, 100);
   RNA_def_property_ui_text(prop, "Segments", "Number of segments for round edges/verts");
-  RNA_def_property_update(prop, 0, "rna_Modifier_update");
+  RNA_def_property_update(prop, 0, "rna_BevelModifier_update_segments");
 
   prop = RNA_def_property(srna, "use_only_vertices", PROP_BOOLEAN, PROP_NONE);
   RNA_def_property_boolean_sdna(prop, NULL, "flags", MOD_BEVEL_VERT);
@@ -3648,7 +3676,7 @@ static void rna_def_modifier_bevel(BlenderRNA *brna)
   prop = RNA_def_property(srna, "offset_type", PROP_ENUM, PROP_NONE);
   RNA_def_property_enum_sdna(prop, NULL, "val_flags");
   RNA_def_property_enum_items(prop, prop_val_type_items);
-  RNA_def_property_ui_text(prop, "Amount Type", "What distance Width measures");
+  RNA_def_property_ui_text(prop, "Width Type", "What distance Width measures");
   RNA_def_property_update(prop, 0, "rna_Modifier_update");
 
   prop = RNA_def_property(srna, "profile", PROP_FLOAT, PROP_FACTOR);
@@ -3693,13 +3721,13 @@ static void rna_def_modifier_bevel(BlenderRNA *brna)
 
   prop = RNA_def_property(srna, "miter_outer", PROP_ENUM, PROP_NONE);
   RNA_def_property_enum_sdna(prop, NULL, "miter_outer");
-  RNA_def_property_enum_items(prop, prop_miter_items);
+  RNA_def_property_enum_items(prop, prop_miter_outer_items);
   RNA_def_property_ui_text(prop, "Outer Miter", "Pattern to use for outside of miters");
   RNA_def_property_update(prop, 0, "rna_Modifier_update");
 
   prop = RNA_def_property(srna, "miter_inner", PROP_ENUM, PROP_NONE);
   RNA_def_property_enum_sdna(prop, NULL, "miter_inner");
-  RNA_def_property_enum_items(prop, prop_miter_items);
+  RNA_def_property_enum_items(prop, prop_miter_inner_items);
   RNA_def_property_ui_text(prop, "Inner Miter", "Pattern to use for inside of miters");
   RNA_def_property_update(prop, 0, "rna_Modifier_update");
 
@@ -3709,6 +3737,25 @@ static void rna_def_modifier_bevel(BlenderRNA *brna)
   RNA_def_property_ui_range(prop, 0.0f, 100.0f, 0.1, 4);
   RNA_def_property_ui_text(prop, "Spread", "Spread distance for inner miter arcs");
   RNA_def_property_update(prop, 0, "rna_Modifier_update");
+
+  prop = RNA_def_property(srna, "use_custom_profile", PROP_BOOLEAN, PROP_NONE);
+  RNA_def_property_boolean_sdna(prop, NULL, "flags", MOD_BEVEL_CUSTOM_PROFILE);
+  RNA_def_property_ui_text(
+      prop, "Custom Profile", "Whether to use a user inputed curve for the bevel's profile");
+  RNA_def_property_update(prop, 0, "rna_Modifier_update");
+
+  prop = RNA_def_property(srna, "custom_profile", PROP_POINTER, PROP_NONE);
+  RNA_def_property_struct_type(prop, "CurveProfile");
+  RNA_def_property_pointer_sdna(prop, NULL, "custom_profile");
+  RNA_def_property_ui_text(prop, "Custom Profile Path", "The path for the custom profile");
+  RNA_def_property_update(prop, 0, "rna_Modifier_update");
+
+  prop = RNA_def_property(srna, "vmesh_method", PROP_ENUM, PROP_NONE);
+  RNA_def_property_enum_sdna(prop, NULL, "vmesh_method");
+  RNA_def_property_enum_items(prop, prop_vmesh_method_items);
+  RNA_def_property_ui_text(
+      prop, "Vertex Mesh Method", "The method to use to create the mesh at intersections");
+  RNA_def_property_update(prop, 0, "rna_Modifier_update");
 }
 
 static void rna_def_modifier_shrinkwrap(BlenderRNA *brna)
index b9bc7b2bbf526c6afa59a15e5a3e919f4114d97f..4d3de4ed6e64a9f3b554a60d3ef3e470bb4d9e11 100644 (file)
@@ -3307,6 +3307,12 @@ static void rna_def_tool_settings(BlenderRNA *brna)
   RNA_def_property_flag(prop, PROP_NEVER_NULL);
   RNA_def_property_struct_type(prop, "MeshStatVis");
   RNA_def_property_ui_text(prop, "Mesh Statistics Visualization", NULL);
+
+  /* CurveProfile */
+  prop = RNA_def_property(srna, "custom_bevel_profile_preset", PROP_POINTER, PROP_NONE);
+  RNA_def_property_pointer_sdna(prop, NULL, "custom_bevel_profile_preset");
+  RNA_def_property_struct_type(prop, "CurveProfile");
+  RNA_def_property_ui_text(prop, "Curve Profile Widget", "Used for defining a profile's path");
 }
 
 static void rna_def_unified_paint_settings(BlenderRNA *brna)
index 94fe43a0a7784ceb98dd9fc6dd2d375c27e258de..c734720fdcd365c95b49b016741b374c77ae3183 100644 (file)
@@ -1220,6 +1220,10 @@ void RNA_api_ui_layout(StructRNA *srna)
   RNA_def_boolean(func, "use_negative_slope", false, "", "Use a negative slope by default");
   RNA_def_boolean(func, "show_tone", false, "", "Show tone options");
 
+  func = RNA_def_function(srna, "template_curveprofile", "uiTemplateCurveProfile");
+  RNA_def_function_ui_description(func, "A profile path editor used for custom profiles");
+  api_ui_item_rna_common(func);
+
   func = RNA_def_function(srna, "template_color_ramp", "uiTemplateColorRamp");
   RNA_def_function_ui_description(func, "Item. A color ramp widget");
   api_ui_item_rna_common(func);
index 0c00bb572be217ca0f5236fd1864727395ad988d..17384f133b194460be30f7cabfbc8cb460ba824a 100644 (file)
@@ -31,6 +31,7 @@
 #include "DNA_meshdata_types.h"
 #include "DNA_object_types.h"
 #include "DNA_scene_types.h"
+#include "DNA_curveprofile_types.h"
 
 #include "BKE_deform.h"
 #include "BKE_mesh.h"
@@ -40,6 +41,7 @@
 
 #include "bmesh.h"
 #include "bmesh_tools.h"
+#include "BKE_curveprofile.h"
 
 #include "DEG_depsgraph_query.h"
 
@@ -47,7 +49,7 @@ static void initData(ModifierData *md)
 {
   BevelModifierData *bmd = (BevelModifierData *)md;
 
-  bmd->value = 0.1f;
+  bmd->value = 1.0f;
   bmd->res = 1;
   bmd->flags = 0;
   bmd->val_flags = MOD_BEVEL_AMT_OFFSET;
@@ -62,11 +64,16 @@ static void initData(ModifierData *md)
   bmd->profile = 0.5f;
   bmd->bevel_angle = DEG2RADF(30.0f);
   bmd->defgrp_name[0] = '\0';
+  bmd->custom_profile = BKE_curveprofile_add(PROF_PRESET_LINE);
 }
 
 static void copyData(const ModifierData *md_src, ModifierData *md_dst, const int flag)
 {
+  const BevelModifierData *bmd_src = (const BevelModifierData *)md_src;
+  BevelModifierData *bmd_dst = (BevelModifierData *)md_dst;
+
   modifier_copyData_generic(md_src, md_dst, flag);
+  bmd_dst->custom_profile = BKE_curveprofile_copy(bmd_src->custom_profile);
 }
 
 static void requiredDataMask(Object *UNUSED(ob),
@@ -109,6 +116,8 @@ static Mesh *applyModifier(ModifierData *md, const ModifierEvalContext *ctx, Mes
   const int miter_outer = bmd->miter_outer;
   const int miter_inner = bmd->miter_inner;
   const float spread = bmd->spread;
+  const bool use_custom_profile = (bmd->flags & MOD_BEVEL_CUSTOM_PROFILE);
+  const int vmesh_method = bmd->vmesh_method;
 
   bm = BKE_mesh_to_bmesh_ex(mesh,
                             &(struct BMeshCreateParams){0},
@@ -210,7 +219,10 @@ static Mesh *applyModifier(ModifierData *md, const ModifierEvalContext *ctx, Mes
                 miter_outer,
                 miter_inner,
                 spread,
-                mesh->smoothresh);
+                mesh->smoothresh,
+                use_custom_profile,
+                bmd->custom_profile,
+                vmesh_method);
 
   result = BKE_mesh_from_bmesh_for_eval_nomain(bm, NULL, mesh);
 
@@ -229,6 +241,18 @@ static bool dependsOnNormals(ModifierData *UNUSED(md))
   return true;
 }
 
+static void freeData(ModifierData *md)
+{
+  BevelModifierData *bmd = (BevelModifierData *)md;
+  BKE_curveprofile_free(bmd->custom_profile);
+}
+
+static bool isDisabled(const Scene *UNUSED(scene), ModifierData *md, bool UNUSED(userRenderParams))
+{
+  BevelModifierData *bmd = (BevelModifierData *)md;
+  return (bmd->value == 0.0f);
+}
+
 ModifierTypeInfo modifierType_Bevel = {
     /* name */ "Bevel",
     /* structName */ "BevelModifierData",
@@ -236,19 +260,16 @@ ModifierTypeInfo modifierType_Bevel = {
     /* type */ eModifierTypeType_Constructive,
     /* flags */ eModifierTypeFlag_AcceptsMesh | eModifierTypeFlag_SupportsEditmode |
         eModifierTypeFlag_EnableInEditmode | eModifierTypeFlag_AcceptsCVs,
-
     /* copyData */ copyData,
-
     /* deformVerts */ NULL,
     /* deformMatrices */ NULL,
     /* deformVertsEM */ NULL,
     /* deformMatricesEM */ NULL,
     /* applyModifier */ applyModifier,
-
     /* initData */ initData,
     /* requiredDataMask */ requiredDataMask,
-    /* freeData */ NULL,
-    /* isDisabled */ NULL,
+    /* freeData */ freeData,
+    /* isDisabled */ isDisabled,
     /* updateDepsgraph */ NULL,
     /* dependsOnTime */ NULL,
     /* dependsOnNormals */ dependsOnNormals,