diff --git a/docs/nodes/curve/curve_index.rst b/docs/nodes/curve/curve_index.rst
index 56799be654fababcb6dedb2114e39feec138f297..e06eb2cac1cdd5989762cd32bd6f8a6def4a3c67 100644
--- a/docs/nodes/curve/curve_index.rst
+++ b/docs/nodes/curve/curve_index.rst
@@ -39,6 +39,7 @@ Curves
cast_curve
curve_range
flip_curve
+ reparametrize
curve_segment
split_curve
eval_curve
diff --git a/docs/nodes/curve/reparametrize.rst b/docs/nodes/curve/reparametrize.rst
new file mode 100644
index 0000000000000000000000000000000000000000..ba679c7c2f55a4e064d03aa048d6adadcfb5cbe2
--- /dev/null
+++ b/docs/nodes/curve/reparametrize.rst
@@ -0,0 +1,30 @@
+Reparametrize Curve
+===================
+
+Functionality
+-------------
+
+Given a Curve, this node generates another Curve object, which represents the
+same curve with another parametrization. The parametrization of the curve is
+changed so that the domain of the curve would be equal to specified ``[T_min;
+T_max]`` interval.
+
+This node may be useful, for example, if you have a curve with domain ``[-1;
+5]``, but another node expects a curve with domain ``[0; 1]``.
+
+Inputs
+------
+
+This node has the following inputs:
+
+* **Curve**. Original curve. This input is mandatory.
+* **NewTMin**, **NewTMax**. Lower and upper bounds of the new curve domain.
+ Default values are 0 and 1.
+
+Outputs
+-------
+
+This node has the following output:
+
+* **Curve**. Reparametrized curve.
+
diff --git a/docs/nodes/surface/reparametrize.rst b/docs/nodes/surface/reparametrize.rst
new file mode 100644
index 0000000000000000000000000000000000000000..1b5c73f9a0819a69a3b3bdd066913a1f0f2d72d5
--- /dev/null
+++ b/docs/nodes/surface/reparametrize.rst
@@ -0,0 +1,32 @@
+Reparametrize Surface
+=====================
+
+Functionality
+-------------
+
+Given a Surface, this node generates another Surface object, which represents
+the same surface with another parametrization. The parametrization is changed
+so that the domain of the new surface would be equal to specified U and V
+intervals.
+
+This node may be useful, for example, if you have a surface with domain ``[0;
+2] x [0; 2*pi]``, but another node expects a surface with domain ``[0; 1] x [0; 1]``.
+
+Inputs
+------
+
+This node has the following inputs:
+
+* **Surface**. The original surface. This input is mandatory.
+* **NewUMin**, **NewUMax**. Lower and upper bounds of the new surface domain in
+ U parameter. The default values are 0 and 1.
+* **NewVMin**, **NewVMax**. Lower and upper bounds of the new surface domain in
+ V parameter. The default values are 0 and 1.
+
+Outputs
+-------
+
+This node has the following output:
+
+* **Surface**. Reparametrized surface.
+
diff --git a/docs/nodes/surface/surface_index.rst b/docs/nodes/surface/surface_index.rst
index e0be8e63e6124e107b72e26b34963edabdd44512..542513af0970453c39c07c9e8cccc75b16874e07 100644
--- a/docs/nodes/surface/surface_index.rst
+++ b/docs/nodes/surface/surface_index.rst
@@ -23,6 +23,7 @@ Surface
subdomain
flip
swap
+ reparametrize
normals
curvatures
gauss_curvature
diff --git a/index.md b/index.md
index c325fca31e35f74a4ca8f78cf1907c94bb172491..0c7e7925ce855b3e9eb95165410a61f6cfc7ff56 100644
--- a/index.md
+++ b/index.md
@@ -79,6 +79,7 @@
SvExConcatCurvesNode
SvExBlendCurvesNode
SvExFlipCurveNode
+ SvReparametrizeCurveNode
SvExSurfaceBoundaryNode
---
SvExCurveEndpointsNode
@@ -117,6 +118,7 @@
SvExSurfaceSubdomainNode
SvFlipSurfaceNode
SvSwapSurfaceNode
+ SvReparametrizeSurfaceNode
SvSurfaceNormalsNode
SvSurfaceGaussCurvatureNode
SvSurfaceCurvaturesNode
diff --git a/nodes/curve/reparametrize.py b/nodes/curve/reparametrize.py
new file mode 100644
index 0000000000000000000000000000000000000000..403b58c9beb47aeda81dd0f07ce314a70019b9ed
--- /dev/null
+++ b/nodes/curve/reparametrize.py
@@ -0,0 +1,66 @@
+
+import numpy as np
+
+import bpy
+from bpy.props import FloatProperty, EnumProperty, BoolProperty, IntProperty
+
+from sverchok.node_tree import SverchCustomTreeNode, throttled
+from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level
+from sverchok.utils.curve import SvCurve, SvReparametrizedCurve
+
+class SvReparametrizeCurveNode(bpy.types.Node, SverchCustomTreeNode):
+ """
+ Triggers: Reparametrize Curve
+ Tooltip: Change parameterization of the curve by linear mapping of T parameter to the new bounds
+ """
+ bl_idname = 'SvReparametrizeCurveNode'
+ bl_label = 'Reparametrize Curve'
+ bl_icon = 'OUTLINER_OB_EMPTY'
+ sv_icon = 'SV_REPARAM_CURVE'
+
+ new_t_min : FloatProperty(
+ name = "New T Min",
+ description = "New lower bound of curve's T parameter",
+ default = 0.0,
+ update = updateNode)
+
+ new_t_max : FloatProperty(
+ name = "New T Max",
+ description = "New upper bound of curve's T parameter",
+ default = 1.0,
+ update = updateNode)
+
+ def sv_init(self, context):
+ self.inputs.new('SvCurveSocket', "Curve")
+ self.inputs.new('SvStringsSocket', "NewTMin").prop_name = 'new_t_min'
+ self.inputs.new('SvStringsSocket', "NewTMax").prop_name = 'new_t_max'
+ self.outputs.new('SvCurveSocket', "Curve")
+
+ def process(self):
+ if not any(socket.is_linked for socket in self.outputs):
+ return
+
+ curve_s = self.inputs['Curve'].sv_get()
+ tmin_s = self.inputs['NewTMin'].sv_get()
+ tmax_s = self.inputs['NewTMax'].sv_get()
+
+ curve_s = ensure_nesting_level(curve_s, 2, data_types=(SvCurve,))
+ tmin_s = ensure_nesting_level(tmin_s, 2)
+ tmax_s = ensure_nesting_level(tmax_s, 2)
+
+ curve_out = []
+ for curves, tmins, tmaxs in zip_long_repeat(curve_s, tmin_s, tmax_s):
+ new_curves = []
+ for curve, t_min, t_max in zip_long_repeat(curves, tmins, tmaxs):
+ new_curve = SvReparametrizedCurve(curve, t_min, t_max)
+ new_curves.append(new_curve)
+ curve_out.append(new_curves)
+
+ self.outputs['Curve'].sv_set(curve_out)
+
+def register():
+ bpy.utils.register_class(SvReparametrizeCurveNode)
+
+def unregister():
+ bpy.utils.unregister_class(SvReparametrizeCurveNode)
+
diff --git a/nodes/surface/reparametrize.py b/nodes/surface/reparametrize.py
new file mode 100644
index 0000000000000000000000000000000000000000..53847b4b581942215574a7aec45487bb98419e07
--- /dev/null
+++ b/nodes/surface/reparametrize.py
@@ -0,0 +1,84 @@
+import numpy as np
+
+import bpy
+from bpy.props import FloatProperty, EnumProperty, BoolProperty, IntProperty
+
+from sverchok.node_tree import SverchCustomTreeNode, throttled
+from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level
+from sverchok.utils.logging import info, exception
+from sverchok.utils.surface import SvSurface, SvReparametrizedSurface
+
+class SvReparametrizeSurfaceNode(bpy.types.Node, SverchCustomTreeNode):
+ """
+ Triggers: Reparametrize Surface
+ Tooltip: Change parametrization of the surface by linear mapping of U, V parameters to the new bounds
+ """
+ bl_idname = 'SvReparametrizeSurfaceNode'
+ bl_label = 'Reparametrize Surface'
+ bl_icon = 'OUTLINER_OB_EMPTY'
+ sv_icon = 'SV_REPARAM_SURFACE'
+
+ new_u_min : FloatProperty(
+ name = "New U Min",
+ description = "New lower bound of surface's U parameter",
+ default = 0.0,
+ update = updateNode)
+
+ new_u_max : FloatProperty(
+ name = "New U Max",
+ description = "New upper bound of surface's U parameter",
+ default = 1.0,
+ update = updateNode)
+
+ new_v_min : FloatProperty(
+ name = "New V Min",
+ description = "New lower bound of surface's V parameter",
+ default = 0.0,
+ update = updateNode)
+
+ new_v_max : FloatProperty(
+ name = "New V Max",
+ description = "New upper bound of surface's V parameter",
+ default = 1.0,
+ update = updateNode)
+
+ def sv_init(self, context):
+ self.inputs.new('SvSurfaceSocket', "Surface")
+ self.inputs.new('SvStringsSocket', "NewUMin").prop_name = 'new_u_min'
+ self.inputs.new('SvStringsSocket', "NewUMax").prop_name = 'new_u_max'
+ self.inputs.new('SvStringsSocket', "NewVMin").prop_name = 'new_v_min'
+ self.inputs.new('SvStringsSocket', "NewVMax").prop_name = 'new_v_max'
+ self.outputs.new('SvSurfaceSocket', "Surface")
+
+ def process(self):
+ if not any(socket.is_linked for socket in self.outputs):
+ return
+
+ surface_s = self.inputs['Surface'].sv_get()
+ umin_s = self.inputs['NewUMin'].sv_get()
+ umax_s = self.inputs['NewUMax'].sv_get()
+ vmin_s = self.inputs['NewVMin'].sv_get()
+ vmax_s = self.inputs['NewVMax'].sv_get()
+
+ surface_s = ensure_nesting_level(surface_s, 2, data_types=(SvSurface,))
+ umin_s = ensure_nesting_level(umin_s, 2)
+ umax_s = ensure_nesting_level(umax_s, 2)
+ vmin_s = ensure_nesting_level(vmin_s, 2)
+ vmax_s = ensure_nesting_level(vmax_s, 2)
+
+ surface_out = []
+ for surfaces, umins, umaxs, vmins, vmaxs in zip_long_repeat(surface_s, umin_s, umax_s, vmin_s, vmax_s):
+ new_surfaces = []
+ for surface, u_min, u_max, v_min, v_max in zip_long_repeat(surfaces, umins, umaxs, vmins, vmaxs):
+ new_surface = SvReparametrizedSurface(surface, u_min, u_max, v_min, v_max)
+ new_surfaces.append(new_surface)
+ surface_out.append(new_surfaces)
+
+ self.outputs['Surface'].sv_set(surface_out)
+
+def register():
+ bpy.utils.register_class(SvReparametrizeSurfaceNode)
+
+def unregister():
+ bpy.utils.unregister_class(SvReparametrizeSurfaceNode)
+
diff --git a/ui/icons/sv_flatten.png b/ui/icons/sv_flatten.png
new file mode 100644
index 0000000000000000000000000000000000000000..2b1a684011616b5c86a1b1bb45bcb1e1a9205a31
Binary files /dev/null and b/ui/icons/sv_flatten.png differ
diff --git a/ui/icons/sv_graft.png b/ui/icons/sv_graft.png
new file mode 100644
index 0000000000000000000000000000000000000000..ce1e7737169655281714ada086572540dd120176
Binary files /dev/null and b/ui/icons/sv_graft.png differ
diff --git a/ui/icons/sv_reparam_curve.png b/ui/icons/sv_reparam_curve.png
new file mode 100644
index 0000000000000000000000000000000000000000..aa7431db957f44c5b027e46c0a5fe538a7e2faa4
Binary files /dev/null and b/ui/icons/sv_reparam_curve.png differ
diff --git a/ui/icons/sv_reparam_surface.png b/ui/icons/sv_reparam_surface.png
new file mode 100644
index 0000000000000000000000000000000000000000..28a00fc2ddaefe4a7ba6120d6e359420adfd6211
Binary files /dev/null and b/ui/icons/sv_reparam_surface.png differ
diff --git a/ui/icons/sv_wrap.png b/ui/icons/sv_wrap.png
new file mode 100644
index 0000000000000000000000000000000000000000..2642ab0c9ca6e3a4fda8db1dea0ddd41c0cedf9e
Binary files /dev/null and b/ui/icons/sv_wrap.png differ
diff --git a/ui/icons/svg/sv_flatten.svg b/ui/icons/svg/sv_flatten.svg
new file mode 100644
index 0000000000000000000000000000000000000000..f2218e515689647e47f9c29e44efb344d56c36b5
--- /dev/null
+++ b/ui/icons/svg/sv_flatten.svg
@@ -0,0 +1,408 @@
+
+
+
+
diff --git a/ui/icons/svg/sv_graft.svg b/ui/icons/svg/sv_graft.svg
new file mode 100644
index 0000000000000000000000000000000000000000..29349ad9f539346533f805a9b1f088be4907a9e6
--- /dev/null
+++ b/ui/icons/svg/sv_graft.svg
@@ -0,0 +1,314 @@
+
+
+
+
diff --git a/ui/icons/svg/sv_reparam_curve.svg b/ui/icons/svg/sv_reparam_curve.svg
new file mode 100644
index 0000000000000000000000000000000000000000..5dab9b5dcb053ab369ca40f2e0f95c7c6319d1d9
--- /dev/null
+++ b/ui/icons/svg/sv_reparam_curve.svg
@@ -0,0 +1,455 @@
+
+
+
+
diff --git a/ui/icons/svg/sv_reparam_surface.svg b/ui/icons/svg/sv_reparam_surface.svg
new file mode 100644
index 0000000000000000000000000000000000000000..c7c637c75b489cdf50c4cbf3d2ae9d1c0e770e79
--- /dev/null
+++ b/ui/icons/svg/sv_reparam_surface.svg
@@ -0,0 +1,258 @@
+
+
+
+
diff --git a/ui/icons/svg/sv_wrap.svg b/ui/icons/svg/sv_wrap.svg
new file mode 100644
index 0000000000000000000000000000000000000000..0494dfe3124976d1ff90833cefc60dee1957ba59
--- /dev/null
+++ b/ui/icons/svg/sv_wrap.svg
@@ -0,0 +1,301 @@
+
+
+
+
diff --git a/utils/curve.py b/utils/curve.py
index 9b48ae43ea370f424e75c0b569cd0feb74bc3264..dc35ea76f2416d926c79b2f623bc63ae1fd35a82 100644
--- a/utils/curve.py
+++ b/utils/curve.py
@@ -685,6 +685,55 @@ class SvFlipCurve(SvCurve):
sign = -sign
return array
+class SvReparametrizedCurve(SvCurve):
+ def __init__(self, curve, new_u_min, new_u_max):
+ self.curve = curve
+ self.new_u_min = new_u_min
+ self.new_u_max = new_u_max
+ if hasattr(curve, 'tangent_delta'):
+ self.tangent_delta = curve.tangent_delta
+ else:
+ self.tangent_delta = 0.001
+
+ def get_u_bounds(self):
+ return self.new_u_min, self.new_u_max
+
+ @property
+ def scale(self):
+ u_min, u_max = self.curve.get_u_bounds()
+ return (u_max - u_min) / (self.new_u_max - self.new_u_min)
+
+ def map_u(self, u):
+ u_min, u_max = self.curve.get_u_bounds()
+ return (u_max - u_min) * (u - self.new_u_min) / (self.new_u_max - self.new_u_min) + u_min
+
+ def evaluate(self, t):
+ return self.curve.evaluate(self.map_u(t))
+
+ def evaluate_array(self, ts):
+ return self.curve.evaluate_array(self.map_u(ts))
+
+ def tangent(self, t):
+ return self.scale * self.curve.tangent(self.map_u(t))
+
+ def tangent_array(self, ts):
+ return self.scale * self.curve.tangent_array(self.map_u(ts))
+
+ def second_derivative_array(self, ts):
+ return self.scale**2 * self.curve.second_derivative_array(self.map_u(ts))
+
+ def third_derivative_array(self, ts):
+ return self.scale**3 * self.curve.third_derivative_array(self.map_u(ts))
+
+ def derivatives_array(self, n, ts):
+ derivs = self.curve.derivatives_array(n, ts)
+ k = self.scale
+ array = []
+ for deriv in derivs:
+ array.append(k * deriv)
+ k = k * self.scale
+ return array
+
class SvCurveSegment(SvCurve):
def __init__(self, curve, u_min, u_max, rescale=False):
self.curve = curve
diff --git a/utils/surface.py b/utils/surface.py
index 418a8234f42bbffc626c91a8f7885b8036599075..c1cc315f4ac80a8c1c3914c3342612ebd067c704 100644
--- a/utils/surface.py
+++ b/utils/surface.py
@@ -581,6 +581,75 @@ class SvSwapSurface(SvSurface):
def normal_array(self, us, vs):
return self.surface.normal_array(vs, us)
+class SvReparametrizedSurface(SvSurface):
+ def __init__(self, surface, new_u_min, new_u_max, new_v_min, new_v_max):
+ self.surface = surface
+ self.new_u_min = new_u_min
+ self.new_u_max = new_u_max
+ self.new_v_min = new_v_min
+ self.new_v_max = new_v_max
+ if hasattr(surface, "normal_delta"):
+ self.normal_delta = surface.normal_delta
+ else:
+ self.normal_delta = 0.001
+
+ def get_u_min(self):
+ return self.new_u_min
+
+ def get_v_min(self):
+ return self.new_v_min
+
+ def get_u_max(self):
+ return self.new_u_max
+
+ def get_v_max(self):
+ return self.new_v_max
+
+ def map_uv(self, u, v):
+ new_u_min, new_u_max = self.new_u_min, self.new_u_max
+ new_v_min, new_v_max = self.new_v_min, self.new_v_max
+
+ u_min, u_max = self.surface.get_u_min(), self.surface.get_u_max()
+ v_min, v_max = self.surface.get_v_min(), self.surface.get_v_max()
+
+ u = (u_max - u_min) * (u - new_u_min) / (new_u_max - new_u_min) + u_min
+ v = (v_max - v_min) * (v - new_v_min) / (new_v_max - new_v_min) + v_min
+
+ return u, v
+
+ def scale_u(self):
+ new_u_min, new_u_max = self.new_u_min, self.new_u_max
+ u_min, u_max = self.surface.get_u_min(), self.surface.get_u_max()
+ return (u_max - u_min) / (new_u_max - new_u_min)
+
+ def scale_v(self):
+ new_v_min, new_v_max = self.new_v_min, self.new_v_max
+ v_min, v_max = self.surface.get_v_min(), self.surface.get_v_max()
+ return (v_max - v_min) / (new_v_max - new_v_min)
+
+ def evaluate(self, u, v):
+ u, v = self.map_uv(u, v)
+ return self.surface.evaluate(u, v)
+
+ def evaluate_array(self, us, vs):
+ us, vs = self.map_uv(us, vs)
+ return self.surface.evaluate_array(us, vs)
+
+ def normal(self, u, v):
+ u, v = self.map_uv(u, v)
+ return self.surface.normal(u, v)
+
+ def normal_array(self, us, vs):
+ us, vs = self.map_uv(us, vs)
+ return self.surface.normal_array(us, vs)
+
+ def derivatives_data_array(self, us, vs):
+ us, vs = self.map_uv(us, vs)
+ data = self.surface.derivatives_data_array(us, vs)
+ data.du *= self.scale_u()
+ data.dv *= self.scale_v()
+ return data
+
class SvPlane(SvSurface):
__description__ = "Plane"