def register(): global logger logger = getLogger("menu") menu, node_count, original_categories = make_categories() temp_details['node_count'] = node_count if hasattr(bpy.types, "SV_PT_NodesTPanel"): unregister_node_panels() categories = [(category.identifier, category.name, category.name, i) for i, category in enumerate(menu)] bpy.types.Scene.sv_selected_category = bpy.props.EnumProperty( name="Category", description="Select nodes category", items=get_all_categories(categories)) bpy.types.Scene.sv_node_search = bpy.props.StringProperty( name="Search", description= "Enter search term and press Enter to search; clear the field to return to selection of node category." ) bpy.utils.register_class(SvResetNodeSearchOperator) register_node_panels("SVERCHOK", menu) build_help_remap(original_categories)
def _intersect_curves_line(curve1, curve2, precision=0.001, logger=None): if logger is None: logger = getLogger() t1_min, t1_max = curve1.get_u_bounds() t2_min, t2_max = curve2.get_u_bounds() v1, v2 = _get_curve_direction(curve1) v3, v4 = _get_curve_direction(curve2) logger.debug(f"Call L: [{t1_min} - {t1_max}] x [{t2_min} - {t2_max}]") r = intersect_segment_segment(v1, v2, v3, v4, tolerance=precision, endpoint_tolerance=0.0) if not r: logger.debug(f"({v1} - {v2}) x ({v3} - {v4}): no intersection") return [] else: u, v, pt = r t1 = (1 - u) * t1_min + u * t1_max t2 = (1 - v) * t2_min + v * t2_max return [(t1, t2, pt)]
def intersect_nurbs_curves(curve1, curve2, method='SLSQP', numeric_precision=0.001, logger=None): if logger is None: logger = getLogger() bbox_tolerance = 1e-4 # "Recursive bounding box" algorithm: # * if bounding boxes of two curves do not intersect, then curves do not intersect # * Otherwise, split each curves in half, and check if bounding boxes of these halves intersect. # * When this subdivision gives very small parts of curves, try to find intersections numerically. # # This implementation depends heavily on the fact that curves are NURBS. Because only NURBS curves # give us a simple way to calculate bounding box of the curve: it's a bounding box of curve's # control points. def _intersect(curve1, curve2, c1_bounds, c2_bounds): if curve1 is None or curve2 is None: return [] t1_min, t1_max = c1_bounds t2_min, t2_max = c2_bounds #logger.debug(f"check: [{t1_min} - {t1_max}] x [{t2_min} - {t2_max}]") bbox1 = curve1.get_bounding_box().increase(bbox_tolerance) bbox2 = curve2.get_bounding_box().increase(bbox_tolerance) if not bbox1.intersects(bbox2): return [] THRESHOLD = 0.01 if bbox1.size() < THRESHOLD and bbox2.size() < THRESHOLD: #if _check_is_line(curve1) and _check_is_line(curve2): return _intersect_curves_equation(curve1, curve2, method=method, precision=numeric_precision) mid1 = (t1_min + t1_max) * 0.5 mid2 = (t2_min + t2_max) * 0.5 c11, c12 = curve1.split_at(mid1) c21, c22 = curve2.split_at(mid2) r1 = _intersect(c11, c21, (t1_min, mid1), (t2_min, mid2)) r2 = _intersect(c11, c22, (t1_min, mid1), (mid2, t2_max)) r3 = _intersect(c12, c21, (mid1, t1_max), (t2_min, mid2)) r4 = _intersect(c12, c22, (mid1, t1_max), (mid2, t2_max)) return r1 + r2 + r3 + r4 return _intersect(curve1, curve2, curve1.get_u_bounds(), curve2.get_u_bounds())
def _intersect_curves_equation(curve1, curve2, method='SLSQP', precision=0.001, logger=None): if logger is None: logger = getLogger() t1_min, t1_max = curve1.get_u_bounds() t2_min, t2_max = curve2.get_u_bounds() def goal(ts): p1 = curve1.evaluate(ts[0]) p2 = curve2.evaluate(ts[1]) r = (p2 - p1).max() return r #return np.array([r, r]) mid1 = (t1_min + t1_max) * 0.5 mid2 = (t2_min + t2_max) * 0.5 x0 = np.array([mid1, mid2]) # def callback(ts, rs): # logger.debug(f"=> {ts} => {rs}") #logger.debug(f"Call R: [{t1_min} - {t1_max}] x [{t2_min} - {t2_max}]") # Find minimum distance between two curves with a numeric method. # If this minimum distance is small enough, we will say that curves # do intersect. res = scipy.optimize.minimize(goal, x0, method=method, bounds=[(t1_min, t1_max), (t2_min, t2_max)], tol=0.5 * precision) if res.success: t1, t2 = tuple(res.x) t1 = np.clip(t1, t1_min, t1_max) t2 = np.clip(t2, t2_min, t2_max) pt1 = curve1.evaluate(t1) pt2 = curve2.evaluate(t2) dist = np.linalg.norm(pt2 - pt1) if dist < precision: #logger.debug(f"Found: T1 {t1}, T2 {t2}, Pt1 {pt1}, Pt2 {pt2}") pt = (pt1 + pt2) * 0.5 return [(t1, t2, pt)] else: logger.debug( f"numeric method found a point, but it's too far: [{t1_min} - {t1_max}] x [{t2_min} - {t2_max}]: {dist}" ) return [] else: logger.debug( f"numeric method fail: [{t1_min} - {t1_max}] x [{t2_min} - {t2_max}]: {res.message}" ) return []
def add_fail(self, fail_name, source=None): """Increase counter of given fail message, also printing error message in debug mode""" try: yield except Exception as e: self._log[fail_name] += 1 logger = getLogger() if logger.isEnabledFor(logging.DEBUG): logger.debug(f'FAIL: "{fail_name}", {"SOURCE: " if source else ""}{source or ""}, {e}') traceback.print_exc()
def _intersect_curves_equation(curve1, curve2, method='SLSQP', precision=0.001, logger=None): if logger is None: logger = getLogger() t1_min, t1_max = curve1.get_u_bounds() t2_min, t2_max = curve2.get_u_bounds() lower = np.array([t1_min, t2_min]) upper = np.array([t1_max, t2_max]) def linear_intersection(): # If both curves look very much like straight line segments, # then we can calculate their intersections by solving simple # linear equations. line1 = _check_is_line(curve1) line2 = _check_is_line(curve2) if line1 and line2: v1, v2 = line1 v3, v4 = line2 logger.debug( f"Call L: [{t1_min} - {t1_max}] x [{t2_min} - {t2_max}]") r = intersect_segment_segment(v1, v2, v3, v4) if not r: logger.debug(f"({v1} - {v2}) x ({v3} - {v4}): no intersection") return None else: u, v, pt = r t1 = (1 - u) * t1_min + u * t1_max t2 = (1 - v) * t2_min + v * t2_max return [(t1, t2, pt)] r = linear_intersection() if r is not None: return r def goal(ts): p1 = curve1.evaluate(ts[0]) p2 = curve2.evaluate(ts[1]) r = (p2 - p1).max() return r #return np.array([r, r]) mid1 = (t1_min + t1_max) * 0.5 mid2 = (t2_min + t2_max) * 0.5 x0 = np.array([mid1, mid2]) # def callback(ts, rs): # logger.debug(f"=> {ts} => {rs}") #logger.debug(f"Call R: [{t1_min} - {t1_max}] x [{t2_min} - {t2_max}]") # Find minimum distance between two curves with a numeric method. # If this minimum distance is small enough, we will say that curves # do intersect. res = scipy.optimize.minimize(goal, x0, method=method, bounds=[(t1_min, t1_max), (t2_min, t2_max)], tol=0.5 * precision) if res.success: t1, t2 = tuple(res.x) t1 = np.clip(t1, t1_min, t1_max) t2 = np.clip(t2, t2_min, t2_max) pt1 = curve1.evaluate(t1) pt2 = curve2.evaluate(t2) dist = np.linalg.norm(pt2 - pt1) if dist < precision: #logger.debug(f"Found: T1 {t1}, T2 {t2}, Pt1 {pt1}, Pt2 {pt2}") pt = (pt1 + pt2) * 0.5 return [(t1, t2, pt)] else: logger.debug( f"numeric method found a point, but it's too far: [{t1_min} - {t1_max}] x [{t2_min} - {t2_max}]: {dist}" ) return [] else: logger.debug( f"numeric method fail: [{t1_min} - {t1_max}] x [{t2_min} - {t2_max}]: {res.message}" ) return []
def nearest_point_on_curve(src_points, curve, samples=10, precise=True, method='Brent', output_points=True, logger=None): """ Find nearest point on any curve. """ if logger is None: logger = getLogger() t_min, t_max = curve.get_u_bounds() def init_guess(curve, points_from): us = np.linspace(t_min, t_max, num=samples) points = curve.evaluate_array(us).tolist() #print("P:", points) kdt = kdtree.KDTree(len(us)) for i, v in enumerate(points): kdt.insert(v, i) kdt.balance() us_out = [] nearest_out = [] for point_from in points_from: nearest, i, distance = kdt.find(point_from) us_out.append(us[i]) nearest_out.append(tuple(nearest)) return us_out, np.array(nearest_out) def goal(t): dv = curve.evaluate(t) - np.array(src_point) return np.linalg.norm(dv) init_ts, init_points = init_guess(curve, src_points) result_ts = [] if precise: for src_point, init_t, init_point in zip(src_points, init_ts, init_points): delta_t = (t_max - t_min) / samples logger.debug("T_min %s, T_max %s, init_t %s, delta_t %s", t_min, t_max, init_t, delta_t) if init_t <= t_min: if init_t - delta_t >= t_min: bracket = (init_t - delta_t, init_t, t_max) else: bracket = None # (t_min, t_min + delta_t, t_min + 2*delta_t) elif init_t >= t_max: if init_t + delta_t <= t_max: bracket = (t_min, init_t, init_t + delta_t) else: bracket = None # (t_max - 2*delta_t, t_max - delta_t, t_max) else: bracket = (t_min, init_t, t_max) result = minimize_scalar(goal, bounds=(t_min, t_max), bracket=bracket, method=method) if not result.success: if hasattr(result, 'message'): message = result.message else: message = repr(result) raise Exception( "Can't find the nearest point for {}: {}".format( src_point, message)) t0 = result.x if t0 < t_min: t0 = t_min elif t0 > t_max: t0 = t_max result_ts.append(t0) else: result_ts = init_ts if output_points: if precise: result_points = curve.evaluate_array(np.array(result_ts)) return list(zip(result_ts, result_points)) else: return list(zip(result_ts, init_points)) else: return result_ts
def register(): global logger bpy.utils.register_class(SvNodeRefreshFromTextEditor) logger = getLogger("text_editor_plugins") add_keymap()
def remove_knot(self, u, count=1, target=None, tolerance=1e-6, if_possible=False): # Implementation adapted from Geomdl logger = getLogger() if (count is None) == (target is None): raise Exception("Either count or target must be specified") orig_multiplicity = sv_knotvector.find_multiplicity( self.get_knotvector(), u) if count == SvNurbsCurve.ALL: count = orig_multiplicity elif count == SvNurbsCurve.ALL_BUT_ONE: count = orig_multiplicity - 1 elif count is None: count = orig_multiplicity - target degree = self.get_degree() order = degree + 1 if not if_possible and (count > orig_multiplicity): raise CantRemoveKnotException( f"Asked to remove knot t={u} for {count} times, but it's multiplicity is only {orig_multiplicity}" ) # Edge case if count < 1: return self def knot_removal_alpha_i(u, knotvector, idx): return (u - knotvector[idx]) / (knotvector[idx + order] - knotvector[idx]) def knot_removal_alpha_j(u, knotvector, idx): return (u - knotvector[idx]) / (knotvector[idx + order] - knotvector[idx]) def point_distance(p1, p2): return np.linalg.norm(p1 - p2) #return np.linalg.norm(np.array(p1) - np.array(p2)) def remove_one_knot(curve): ctrlpts = curve.get_homogenous_control_points() N = len(ctrlpts) knotvector = curve.get_knotvector() orig_multiplicity = sv_knotvector.find_multiplicity(knotvector, u) knot_span = sv_knotvector.find_span(knotvector, N, u) # Initialize variables first = knot_span - degree last = knot_span - orig_multiplicity # Don't change input variables, prepare new ones for updating ctrlpts_new = deepcopy(ctrlpts) # Initialize temp array for storing new control points temp_i = np.zeros((2 * degree + 1, 4)) temp_j = np.zeros((2 * degree + 1, 4)) removed_count = 0 # Loop for Eqs 5.28 & 5.29 t = 0 offset = first - 1 # difference in index between `temp` and ctrlpts temp_i[0] = ctrlpts[offset] temp_j[last + 1 - offset] = ctrlpts[last + 1] i = first j = last ii = 1 jj = last - offset can_remove = False # Compute control points for one removal step while j - i > t: alpha_i = knot_removal_alpha_i(u, knotvector, i) alpha_j = knot_removal_alpha_j(u, knotvector, j) temp_i[ii] = (ctrlpts[i] - (1.0 - alpha_i) * temp_i[ii - 1]) / alpha_i temp_j[jj] = (ctrlpts[j] - alpha_j * temp_j[jj + 1]) / (1.0 - alpha_j) i += 1 j -= 1 ii += 1 jj -= 1 # Check if the knot is removable if j - i < t: dist = point_distance(temp_i[ii - 1], temp_j[jj + 1]) if dist <= tolerance: can_remove = True else: logger.debug(f"remove_knot: stop, distance={dist}") else: alpha_i = knot_removal_alpha_i(u, knotvector, i) ptn = alpha_i * temp_j[ii + t + 1] + (1.0 - alpha_i) * temp_i[ii - 1] dist = point_distance(ctrlpts[i], ptn) if dist <= tolerance: can_remove = True else: logger.debug(f"remove_knot: stop, distance={dist}") # Check if we can remove the knot and update new control points array if can_remove: i = first j = last while j - i > t: ctrlpts_new[i] = temp_i[i - offset] ctrlpts_new[j] = temp_j[j - offset] i += 1 j -= 1 # Update indices first -= 1 last += 1 removed_count += 1 else: raise CantRemoveKnotException() new_kv = np.copy(curve.get_knotvector()) if removed_count > 0: m = N + degree + 1 for k in range(knot_span + 1, m): new_kv[k - removed_count] = new_kv[k] new_kv = new_kv[:m - removed_count] #new_kv = np.delete(curve.get_knotvector(), np.s_[(r-t+1):(r+1)]) # Shift control points (refer to p.183 of The NURBS Book, 2nd Edition) j = int((2 * knot_span - orig_multiplicity - degree) / 2) # first control point out i = j for k in range(1, removed_count): if k % 2 == 1: i += 1 else: j -= 1 for k in range(i + 1, N): ctrlpts_new[j] = ctrlpts_new[k] j += 1 # Slice to get the new control points ctrlpts_new = ctrlpts_new[0:-removed_count] ctrlpts_new = np.array(ctrlpts_new) control_points, weights = from_homogenous(ctrlpts_new) return curve.copy(knotvector=new_kv, control_points=control_points, weights=weights) curve = self removed_count = 0 for i in range(count): try: curve = remove_one_knot(curve) removed_count += 1 except CantRemoveKnotException as e: break if not if_possible and (removed_count < count): raise CantRemoveKnotException( f"Asked to remove knot t={u} for {count} times, but could remove it only {removed_count} times" ) #print(f"Removed knot t={u} for {removed_count} times") return curve
def intersect_nurbs_curves(curve1, curve2, method='SLSQP', numeric_precision=0.001, logger=None): if logger is None: logger = getLogger() u1_min, u1_max = curve1.get_u_bounds() u2_min, u2_max = curve2.get_u_bounds() expected_subdivisions = 10 max_dt1 = (u1_max - u1_min) / expected_subdivisions max_dt2 = (u2_max - u2_min) / expected_subdivisions # Float precision problems workaround bbox_tolerance = 1e-4 # "Recursive bounding box" algorithm: # * if bounding boxes of two curves do not intersect, then curves do not intersect # * Otherwise, split each curves in half, and check if bounding boxes of these halves intersect. # * When this subdivision gives very small parts of curves, try to find intersections numerically. # # This implementation depends heavily on the fact that curves are NURBS. Because only NURBS curves # give us a simple way to calculate bounding box of the curve: it's a bounding box of curve's # control points. def _intersect(curve1, curve2, c1_bounds, c2_bounds, i=0): if curve1 is None or curve2 is None: return [] t1_min, t1_max = c1_bounds t2_min, t2_max = c2_bounds #logger.debug(f"check: [{t1_min} - {t1_max}] x [{t2_min} - {t2_max}]") bbox1 = curve1.get_bounding_box().increase(bbox_tolerance) bbox2 = curve2.get_bounding_box().increase(bbox_tolerance) if not bbox1.intersects(bbox2): return [] r = _intersect_endpoints(curve1, curve2, numeric_precision) if r: logger.debug( "Endpoint intersection after %d iterations; bbox1: %s, bbox2: %s", i, bbox1.size(), bbox2.size()) return [r] THRESHOLD = 0.02 if curve1.is_line(numeric_precision) and curve2.is_line( numeric_precision): logger.debug("Calling Lin() after %d iterations", i) r = _intersect_curves_line(curve1, curve2, numeric_precision, logger=logger) if r: return r if bbox1.size() < THRESHOLD and bbox2.size() < THRESHOLD: logger.debug("Calling Eq() after %d iterations", i) return _intersect_curves_equation(curve1, curve2, method=method, precision=numeric_precision, logger=logger) mid1 = (t1_min + t1_max) * 0.5 mid2 = (t2_min + t2_max) * 0.5 c11, c12 = curve1.split_at(mid1) c21, c22 = curve2.split_at(mid2) r1 = _intersect(c11, c21, (t1_min, mid1), (t2_min, mid2), i + 1) r2 = _intersect(c11, c22, (t1_min, mid1), (mid2, t2_max), i + 1) r3 = _intersect(c12, c21, (mid1, t1_max), (t2_min, mid2), i + 1) r4 = _intersect(c12, c22, (mid1, t1_max), (mid2, t2_max), i + 1) return r1 + r2 + r3 + r4 return _intersect(curve1, curve2, curve1.get_u_bounds(), curve2.get_u_bounds())