Tissue: Update to 3.52
From https://github.com/alessandro-zomparelli/tissue/releases/tag/v0-3-52 with https://github.com/alessandro-zomparelli/tissue/pull/131 applied. Fixes T80702 Fixes T73136
This commit is contained in:
commit
3cd4141bdc
Notes:
blender-bot
2023-02-14 19:02:15 +01:00
Referenced by issue #80702, Tissues add-on Lag after use Referenced by issue #73136, Built in Tissue Tools addon slows down blender
|
@ -0,0 +1,47 @@
|
|||
# Tissue
|
||||
![cover](http://www.co-de-it.com/wordpress/wp-content/uploads/2015/07/tissue_graphics.jpg)
|
||||
Tissue - Blender's add-on for computational design by Co-de-iT
|
||||
http://www.co-de-it.com/wordpress/code/blender-tissue
|
||||
|
||||
Tissue is already shipped with both Blender. However I recommend to update the default version downloading manually the most recent one, for more updated features and more stability.
|
||||
|
||||
### Blender 2.93
|
||||
|
||||
Tissue v0.3.52 for Blender 2.93 (latest stable release): https://github.com/alessandro-zomparelli/tissue/releases/tag/v0-3-52
|
||||
|
||||
Development branch (usually the most updated version): https://github.com/alessandro-zomparelli/tissue/tree/b290-dev
|
||||
|
||||
### Blender 2.79 (unsupported)
|
||||
|
||||
Tissue v0.3.4 for Blender 2.79b (latest stable release): https://github.com/alessandro-zomparelli/tissue/releases/tag/v0-3-4
|
||||
|
||||
Development branch (most updated version): https://github.com/alessandro-zomparelli/tissue/tree/dev1
|
||||
|
||||
|
||||
### Installation:
|
||||
|
||||
1. Start Blender. Go to "Edit" and then "Preferences"
|
||||
2. Open the "Add-ons" preferences
|
||||
3. Click "install..." and point Blender at the downloaded zip file (on OSX it may have extracted the zip automatically, that won't work, so you have to zip the extracted folder again)
|
||||
4. You may see now two different versions of Tissue, activate only the second one and ignore the first one
|
||||
|
||||
### Documentation
|
||||
|
||||
Tissue documentation for Blender 2.80: https://github.com/alessandro-zomparelli/tissue/wiki
|
||||
|
||||
|
||||
### Issues
|
||||
Please help me keeping Tissue stable and updated, report any issues or feedback here: https://github.com/alessandro-zomparelli/tissue/issues
|
||||
|
||||
### Contribute
|
||||
Tissue is free and open-source. I really think that this is the power of Blender and I wanted to give my small contribution to it.
|
||||
|
||||
If you like my work and you want to help me, please consider to support me on **Patreon**, where I share some tips about Blender, Tissue and scripting: https://www.patreon.com/alessandrozomparelli
|
||||
|
||||
[![Patreon](http://alessandrozomparelli.com/wp-content/uploads/2020/04/patreon-transparent-vector-small.png)](https://www.patreon.com/alessandrozomparelli)
|
||||
|
||||
A special thanks to all my patrons, in particular to my **Tissue Supporters**: *TomaLaboratory*, *Scott Shorter*, *Garrett Post*, *Kairomon*, *Art Evans*, *Justin Davis*, *John Wise*, *Avi Bryant*, *Ahmed Saber*, *SlimeSound Production*, *Steffen Meier*.
|
||||
|
||||
Many thanks,
|
||||
|
||||
Alessandro
|
|
@ -17,9 +17,9 @@
|
|||
bl_info = {
|
||||
"name": "Tissue",
|
||||
"author": "Alessandro Zomparelli (Co-de-iT)",
|
||||
"version": (0, 3, 25),
|
||||
"blender": (2, 80, 0),
|
||||
"location": "Sidebar > Edit Tab",
|
||||
"version": (0, 3, 52),
|
||||
"blender": (2, 93, 0),
|
||||
"location": "",
|
||||
"description": "Tools for Computational Design",
|
||||
"warning": "",
|
||||
"doc_url": "{BLENDER_MANUAL_URL}/addons/mesh/tissue.html",
|
||||
|
@ -31,59 +31,107 @@ bl_info = {
|
|||
if "bpy" in locals():
|
||||
import importlib
|
||||
importlib.reload(tessellate_numpy)
|
||||
importlib.reload(colors_groups_exchanger)
|
||||
importlib.reload(tissue_properties)
|
||||
importlib.reload(weight_tools)
|
||||
importlib.reload(dual_mesh)
|
||||
importlib.reload(lattice)
|
||||
importlib.reload(uv_to_mesh)
|
||||
importlib.reload(utils)
|
||||
importlib.reload(config)
|
||||
importlib.reload(material_tools)
|
||||
importlib.reload(curves_tools)
|
||||
importlib.reload(polyhedra)
|
||||
|
||||
else:
|
||||
from . import tessellate_numpy
|
||||
from . import colors_groups_exchanger
|
||||
from . import tissue_properties
|
||||
from . import weight_tools
|
||||
from . import dual_mesh
|
||||
from . import lattice
|
||||
from . import uv_to_mesh
|
||||
from . import utils
|
||||
from . import config
|
||||
from . import material_tools
|
||||
from . import curves_tools
|
||||
from . import polyhedra
|
||||
|
||||
import bpy
|
||||
from bpy.props import PointerProperty, CollectionProperty, BoolProperty
|
||||
|
||||
classes = (
|
||||
tessellate_numpy.tissue_tessellate_prop,
|
||||
tessellate_numpy.tessellate,
|
||||
tessellate_numpy.update_tessellate,
|
||||
tessellate_numpy.TISSUE_PT_tessellate,
|
||||
tessellate_numpy.rotate_face,
|
||||
tessellate_numpy.TISSUE_PT_tessellate_object,
|
||||
|
||||
colors_groups_exchanger.face_area_to_vertex_groups,
|
||||
colors_groups_exchanger.vertex_colors_to_vertex_groups,
|
||||
colors_groups_exchanger.vertex_group_to_vertex_colors,
|
||||
colors_groups_exchanger.TISSUE_PT_weight,
|
||||
colors_groups_exchanger.TISSUE_PT_color,
|
||||
colors_groups_exchanger.weight_contour_curves,
|
||||
colors_groups_exchanger.weight_contour_mask,
|
||||
colors_groups_exchanger.weight_contour_displace,
|
||||
colors_groups_exchanger.harmonic_weight,
|
||||
colors_groups_exchanger.edges_deformation,
|
||||
colors_groups_exchanger.edges_bending,
|
||||
colors_groups_exchanger.weight_laplacian,
|
||||
colors_groups_exchanger.reaction_diffusion,
|
||||
colors_groups_exchanger.start_reaction_diffusion,
|
||||
colors_groups_exchanger.TISSUE_PT_reaction_diffusion,
|
||||
colors_groups_exchanger.reset_reaction_diffusion_weight,
|
||||
colors_groups_exchanger.formula_prop,
|
||||
colors_groups_exchanger.reaction_diffusion_prop,
|
||||
colors_groups_exchanger.weight_formula,
|
||||
colors_groups_exchanger.curvature_to_vertex_groups,
|
||||
colors_groups_exchanger.weight_formula_wiki,
|
||||
classes = (
|
||||
config.tissuePreferences,
|
||||
config.tissue_install_numba,
|
||||
|
||||
tissue_properties.tissue_prop,
|
||||
tissue_properties.tissue_tessellate_prop,
|
||||
tessellate_numpy.tissue_tessellate,
|
||||
tessellate_numpy.tissue_update_tessellate,
|
||||
tessellate_numpy.tissue_update_tessellate_deps,
|
||||
tessellate_numpy.TISSUE_PT_tessellate,
|
||||
tessellate_numpy.tissue_rotate_face_left,
|
||||
tessellate_numpy.tissue_rotate_face_right,
|
||||
tessellate_numpy.tissue_rotate_face_flip,
|
||||
tessellate_numpy.TISSUE_PT_tessellate_object,
|
||||
tessellate_numpy.TISSUE_PT_tessellate_frame,
|
||||
tessellate_numpy.TISSUE_PT_tessellate_component,
|
||||
tessellate_numpy.TISSUE_PT_tessellate_thickness,
|
||||
tessellate_numpy.TISSUE_PT_tessellate_direction,
|
||||
tessellate_numpy.TISSUE_PT_tessellate_options,
|
||||
tessellate_numpy.TISSUE_PT_tessellate_coordinates,
|
||||
tessellate_numpy.TISSUE_PT_tessellate_rotation,
|
||||
tessellate_numpy.TISSUE_PT_tessellate_selective,
|
||||
tessellate_numpy.TISSUE_PT_tessellate_morphing,
|
||||
tessellate_numpy.TISSUE_PT_tessellate_iterations,
|
||||
tessellate_numpy.tissue_render_animation,
|
||||
|
||||
weight_tools.face_area_to_vertex_groups,
|
||||
weight_tools.vertex_colors_to_vertex_groups,
|
||||
weight_tools.vertex_group_to_vertex_colors,
|
||||
weight_tools.vertex_group_to_uv,
|
||||
weight_tools.TISSUE_PT_weight,
|
||||
weight_tools.TISSUE_PT_color,
|
||||
weight_tools.weight_contour_curves,
|
||||
weight_tools.tissue_weight_contour_curves_pattern,
|
||||
weight_tools.weight_contour_mask,
|
||||
weight_tools.weight_contour_displace,
|
||||
weight_tools.harmonic_weight,
|
||||
weight_tools.edges_deformation,
|
||||
weight_tools.edges_bending,
|
||||
weight_tools.weight_laplacian,
|
||||
weight_tools.reaction_diffusion,
|
||||
weight_tools.start_reaction_diffusion,
|
||||
weight_tools.TISSUE_PT_reaction_diffusion,
|
||||
weight_tools.TISSUE_PT_reaction_diffusion_weight,
|
||||
weight_tools.reset_reaction_diffusion_weight,
|
||||
weight_tools.formula_prop,
|
||||
weight_tools.reaction_diffusion_prop,
|
||||
weight_tools.weight_formula,
|
||||
weight_tools.update_weight_formula,
|
||||
weight_tools.curvature_to_vertex_groups,
|
||||
weight_tools.weight_formula_wiki,
|
||||
weight_tools.tissue_weight_distance,
|
||||
weight_tools.random_weight,
|
||||
weight_tools.bake_reaction_diffusion,
|
||||
weight_tools.reaction_diffusion_free_data,
|
||||
weight_tools.tissue_weight_streamlines,
|
||||
|
||||
dual_mesh.dual_mesh,
|
||||
dual_mesh.dual_mesh_tessellated,
|
||||
|
||||
lattice.lattice_along_surface,
|
||||
|
||||
uv_to_mesh.uv_to_mesh
|
||||
material_tools.random_materials,
|
||||
material_tools.weight_to_materials,
|
||||
|
||||
curves_tools.tissue_to_curve_prop,
|
||||
curves_tools.tissue_convert_to_curve,
|
||||
curves_tools.tissue_convert_to_curve_update,
|
||||
curves_tools.TISSUE_PT_convert_to_curve,
|
||||
|
||||
uv_to_mesh.uv_to_mesh,
|
||||
|
||||
polyhedra.polyhedra_wireframe
|
||||
)
|
||||
|
||||
def register():
|
||||
|
@ -91,28 +139,29 @@ def register():
|
|||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
#bpy.utils.register_module(__name__)
|
||||
bpy.types.Object.tissue = PointerProperty(
|
||||
type=tissue_properties.tissue_prop
|
||||
)
|
||||
bpy.types.Object.tissue_tessellate = PointerProperty(
|
||||
type=tessellate_numpy.tissue_tessellate_prop
|
||||
type=tissue_properties.tissue_tessellate_prop
|
||||
)
|
||||
bpy.types.Object.tissue_to_curve = PointerProperty(
|
||||
type=curves_tools.tissue_to_curve_prop
|
||||
)
|
||||
bpy.types.Object.formula_settings = CollectionProperty(
|
||||
type=colors_groups_exchanger.formula_prop
|
||||
type=weight_tools.formula_prop
|
||||
)
|
||||
bpy.types.Object.reaction_diffusion_settings = PointerProperty(
|
||||
type=colors_groups_exchanger.reaction_diffusion_prop
|
||||
type=weight_tools.reaction_diffusion_prop
|
||||
)
|
||||
# colors_groups_exchanger
|
||||
bpy.app.handlers.frame_change_post.append(colors_groups_exchanger.reaction_diffusion_def)
|
||||
# weight_tools
|
||||
bpy.app.handlers.frame_change_post.append(weight_tools.reaction_diffusion_def)
|
||||
#bpy.app.handlers.frame_change_post.append(tessellate_numpy.anim_tessellate)
|
||||
|
||||
def unregister():
|
||||
from bpy.utils import unregister_class
|
||||
for cls in classes:
|
||||
bpy.utils.unregister_class(cls)
|
||||
#tessellate_numpy.unregister()
|
||||
#colors_groups_exchanger.unregister()
|
||||
#dual_mesh.unregister()
|
||||
#lattice.unregister()
|
||||
#uv_to_mesh.unregister()
|
||||
|
||||
del bpy.types.Object.tissue_tessellate
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,63 @@
|
|||
import bpy
|
||||
from bpy.props import (
|
||||
IntProperty,
|
||||
BoolProperty
|
||||
)
|
||||
|
||||
evaluatedDepsgraph = None
|
||||
|
||||
class tissuePreferences(bpy.types.AddonPreferences):
|
||||
bl_idname = __package__
|
||||
|
||||
print_stats : IntProperty(
|
||||
name="Print Stats",
|
||||
description="Print in the console all details about the computing time.",
|
||||
default=1,
|
||||
min=0,
|
||||
max=4
|
||||
)
|
||||
|
||||
use_numba_tess : BoolProperty(
|
||||
name="Numba Tessellate",
|
||||
description="Boost the Tessellation using Numba module. It will be slower during the first execution",
|
||||
default=True
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
|
||||
from .utils_pip import Pip
|
||||
Pip._ensure_user_site_package()
|
||||
layout = self.layout
|
||||
layout.prop(self, "print_stats")
|
||||
import importlib
|
||||
numba_spec = importlib.util.find_spec('numba')
|
||||
found = numba_spec is not None
|
||||
if found:
|
||||
layout.label(text='Numba module installed correctly!', icon='INFO')
|
||||
layout.prop(self, "use_numba_tess")
|
||||
else:
|
||||
layout.label(text='Numba module not installed!', icon='ERROR')
|
||||
layout.label(text='Installing Numba will make Tissue faster', icon='INFO')
|
||||
row = layout.row()
|
||||
row.operator('scene.tissue_install_numba')
|
||||
layout.label(text='Internet connection required. It may take few minutes', icon='URL')
|
||||
|
||||
class tissue_install_numba(bpy.types.Operator):
|
||||
bl_idname = "scene.tissue_install_numba"
|
||||
bl_label = "Install Numba"
|
||||
bl_description = ("Install Numba python module")
|
||||
bl_options = {'REGISTER'}
|
||||
|
||||
def execute(self, context):
|
||||
try:
|
||||
from .utils_pip import Pip
|
||||
#Pip.upgrade_pip()
|
||||
Pip.install('llvmlite')
|
||||
Pip.install('numba')
|
||||
from numba import jit, njit, guvectorize, float64, int32, prange
|
||||
bool_numba = True
|
||||
print('Tissue: Numba successfully installed!')
|
||||
self.report({'INFO'}, 'Tissue: Numba successfully installed!')
|
||||
except:
|
||||
print('Tissue: Numba not loaded correctly. Try restarting Blender')
|
||||
return {'FINISHED'}
|
|
@ -0,0 +1,803 @@
|
|||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# #
|
||||
# (c) Alessandro Zomparelli #
|
||||
# (2017) #
|
||||
# #
|
||||
# http://www.co-de-it.com/ #
|
||||
# #
|
||||
# ############################################################################ #
|
||||
|
||||
|
||||
import bpy, bmesh
|
||||
from bpy.types import Operator
|
||||
from bpy.props import (
|
||||
IntProperty,
|
||||
BoolProperty,
|
||||
EnumProperty,
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
FloatProperty
|
||||
)
|
||||
from bpy.types import (
|
||||
Operator,
|
||||
Panel,
|
||||
PropertyGroup,
|
||||
)
|
||||
|
||||
import numpy as np
|
||||
from mathutils import Vector
|
||||
from math import pi
|
||||
from .utils import (
|
||||
find_curves,
|
||||
update_curve_from_pydata,
|
||||
simple_to_mesh,
|
||||
convert_object_to_mesh,
|
||||
get_weight_numpy,
|
||||
loops_from_bmesh,
|
||||
get_mesh_before_subs
|
||||
)
|
||||
import time
|
||||
|
||||
|
||||
def anim_curve_active(self, context):
|
||||
ob = context.object
|
||||
props = ob.tissue_to_curve
|
||||
try:
|
||||
props.object.name
|
||||
if not ob.tissue.bool_lock:
|
||||
bpy.ops.object.tissue_convert_to_curve_update()
|
||||
except: pass
|
||||
|
||||
|
||||
class tissue_to_curve_prop(PropertyGroup):
|
||||
object : PointerProperty(
|
||||
type=bpy.types.Object,
|
||||
name="",
|
||||
description="Source object",
|
||||
update = anim_curve_active
|
||||
)
|
||||
bool_smooth : BoolProperty(
|
||||
name="Smooth Shading",
|
||||
default=True,
|
||||
description="Output faces with smooth shading rather than flat shaded",
|
||||
update = anim_curve_active
|
||||
)
|
||||
bool_lock : BoolProperty(
|
||||
name="Lock",
|
||||
description="Prevent automatic update on settings changes or if other objects have it in the hierarchy.",
|
||||
default=False,
|
||||
update = anim_curve_active
|
||||
)
|
||||
bool_dependencies : BoolProperty(
|
||||
name="Update Dependencies",
|
||||
description="Automatically updates source object as well, when possible",
|
||||
default=False,
|
||||
update = anim_curve_active
|
||||
)
|
||||
bool_run : BoolProperty(
|
||||
name="Animatable Curve",
|
||||
description="Automatically recompute the conversion when the frame is changed.",
|
||||
default = False
|
||||
)
|
||||
use_modifiers : BoolProperty(
|
||||
name="Use Modifiers",
|
||||
default=True,
|
||||
description="Automatically apply Modifiers and Shape Keys",
|
||||
update = anim_curve_active
|
||||
)
|
||||
subdivision_mode : EnumProperty(
|
||||
items=(
|
||||
('ALL', "All", ""),
|
||||
('CAGE', "Cage", ""),
|
||||
('INNER', "Inner", "")
|
||||
),
|
||||
default='CAGE',
|
||||
name="Subdivided Edges",
|
||||
update = anim_curve_active
|
||||
)
|
||||
use_endpoint_u : BoolProperty(
|
||||
name="Endpoint U",
|
||||
default=True,
|
||||
description="Make all open nurbs curve meet the endpoints",
|
||||
update = anim_curve_active
|
||||
)
|
||||
clean_distance : FloatProperty(
|
||||
name="Merge Distance", default=0, min=0, soft_max=10,
|
||||
description="Merge Distance",
|
||||
update = anim_curve_active
|
||||
)
|
||||
nurbs_order : IntProperty(
|
||||
name="Order", default=4, min=2, max=6,
|
||||
description="Nurbs order",
|
||||
update = anim_curve_active
|
||||
)
|
||||
system : IntProperty(
|
||||
name="System", default=0, min=0,
|
||||
description="Particle system index",
|
||||
update = anim_curve_active
|
||||
)
|
||||
bounds_selection : EnumProperty(
|
||||
items=(
|
||||
('ALL', "All", ""),
|
||||
('BOUNDS', "Boundaries", ""),
|
||||
('INNER', "Inner", "")
|
||||
),
|
||||
default='ALL',
|
||||
name="Boundary Selection",
|
||||
update = anim_curve_active
|
||||
)
|
||||
periodic_selection : EnumProperty(
|
||||
items=(
|
||||
('ALL', "All", ""),
|
||||
('OPEN', "Open", ""),
|
||||
('CLOSED', "Closed", "")
|
||||
),
|
||||
default='ALL',
|
||||
name="Periodic Selection",
|
||||
update = anim_curve_active
|
||||
)
|
||||
spline_type : EnumProperty(
|
||||
items=(
|
||||
('POLY', "Poly", ""),
|
||||
('BEZIER', "Bezier", ""),
|
||||
('NURBS', "NURBS", "")
|
||||
),
|
||||
default='POLY',
|
||||
name="Spline Type",
|
||||
update = anim_curve_active
|
||||
)
|
||||
mode : EnumProperty(
|
||||
items=(
|
||||
('LOOPS', "Loops", ""),
|
||||
('EDGES', "Edges", ""),
|
||||
('PARTICLES', "Particles", "")
|
||||
),
|
||||
default='LOOPS',
|
||||
name="Conversion Mode",
|
||||
update = anim_curve_active
|
||||
)
|
||||
vertex_group : StringProperty(
|
||||
name="Radius", default='',
|
||||
description="Vertex Group used for variable radius",
|
||||
update = anim_curve_active
|
||||
)
|
||||
invert_vertex_group : BoolProperty(default=False,
|
||||
description='Inverte the value of the Vertex Group',
|
||||
update = anim_curve_active
|
||||
)
|
||||
vertex_group_factor : FloatProperty(
|
||||
name="Factor",
|
||||
default=0,
|
||||
min=0,
|
||||
max=1,
|
||||
description="Depth bevel factor to use for zero vertex group influence",
|
||||
update = anim_curve_active
|
||||
)
|
||||
only_sharp : BoolProperty(
|
||||
default=False,
|
||||
name="Only Sharp Edges",
|
||||
description='Convert only Sharp edges',
|
||||
update = anim_curve_active
|
||||
)
|
||||
pattern_depth : FloatProperty(
|
||||
name="Depth",
|
||||
default=0.02,
|
||||
min=0,
|
||||
soft_max=10,
|
||||
description="Displacement pattern depth",
|
||||
update = anim_curve_active
|
||||
)
|
||||
pattern_offset : FloatProperty(
|
||||
name="Offset",
|
||||
default=0,
|
||||
soft_min=-1,
|
||||
soft_max=1,
|
||||
description="Displacement pattern offset",
|
||||
update = anim_curve_active
|
||||
)
|
||||
pattern0 : IntProperty(
|
||||
name="Step 0",
|
||||
default=0,
|
||||
min=0,
|
||||
soft_max=10,
|
||||
description="Pattern step 0",
|
||||
update = anim_curve_active
|
||||
)
|
||||
pattern1 : IntProperty(
|
||||
name="Step 1",
|
||||
default=0,
|
||||
min=0,
|
||||
soft_max=10,
|
||||
description="Pattern step 1",
|
||||
update = anim_curve_active
|
||||
)
|
||||
|
||||
class tissue_convert_to_curve(Operator):
|
||||
bl_idname = "object.tissue_convert_to_curve"
|
||||
bl_label = "Tissue Convert to Curve"
|
||||
bl_description = "Convert selected mesh to Curve object"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
object : StringProperty(
|
||||
name="",
|
||||
description="Source object",
|
||||
default = ""
|
||||
)
|
||||
bool_smooth : BoolProperty(
|
||||
name="Smooth Shading",
|
||||
default=True,
|
||||
description="Output faces with smooth shading rather than flat shaded"
|
||||
)
|
||||
use_modifiers : BoolProperty(
|
||||
name="Use Modifiers",
|
||||
default=True,
|
||||
description="Automatically apply Modifiers and Shape Keys"
|
||||
)
|
||||
subdivision_mode : EnumProperty(
|
||||
items=(
|
||||
('ALL', "All", ""),
|
||||
('CAGE', "Cage", ""),
|
||||
('INNER', "Inner", "")
|
||||
),
|
||||
default='CAGE',
|
||||
name="Subdivided Edges"
|
||||
)
|
||||
use_endpoint_u : BoolProperty(
|
||||
name="Endpoint U",
|
||||
default=True,
|
||||
description="Make all open nurbs curve meet the endpoints"
|
||||
)
|
||||
nurbs_order : IntProperty(
|
||||
name="Order", default=4, min=2, max=6,
|
||||
description="Nurbs order"
|
||||
)
|
||||
system : IntProperty(
|
||||
name="System", default=0, min=0,
|
||||
description="Particle system index"
|
||||
)
|
||||
clean_distance : FloatProperty(
|
||||
name="Merge Distance", default=0, min=0, soft_max=10,
|
||||
description="Merge Distance"
|
||||
)
|
||||
spline_type : EnumProperty(
|
||||
items=(
|
||||
('POLY', "Poly", ""),
|
||||
('BEZIER', "Bezier", ""),
|
||||
('NURBS', "NURBS", "")
|
||||
),
|
||||
default='POLY',
|
||||
name="Spline Type"
|
||||
)
|
||||
bounds_selection : EnumProperty(
|
||||
items=(
|
||||
('ALL', "All", ""),
|
||||
('BOUNDS', "Boundaries", ""),
|
||||
('INNER', "Inner", "")
|
||||
),
|
||||
default='ALL',
|
||||
name="Boundary Selection"
|
||||
)
|
||||
periodic_selection : EnumProperty(
|
||||
items=(
|
||||
('ALL', "All", ""),
|
||||
('OPEN', "Open", ""),
|
||||
('CLOSED', "Closed", "")
|
||||
),
|
||||
default='ALL',
|
||||
name="Periodic Selection"
|
||||
)
|
||||
mode : EnumProperty(
|
||||
items=(
|
||||
('LOOPS', "Loops", ""),
|
||||
('EDGES', "Edges", ""),
|
||||
('PARTICLES', "Particles", "")
|
||||
),
|
||||
default='LOOPS',
|
||||
name="Conversion Mode"
|
||||
)
|
||||
vertex_group : StringProperty(
|
||||
name="Radius", default='',
|
||||
description="Vertex Group used for variable radius"
|
||||
)
|
||||
invert_vertex_group : BoolProperty(default=False,
|
||||
description='Inverte the value of the Vertex Group'
|
||||
)
|
||||
vertex_group_factor : FloatProperty(
|
||||
name="Factor",
|
||||
default=0,
|
||||
min=0,
|
||||
max=1,
|
||||
description="Depth bevel factor to use for zero vertex group influence"
|
||||
)
|
||||
only_sharp : BoolProperty(
|
||||
default=False,
|
||||
name="Only Sharp Edges",
|
||||
description='Convert only Sharp edges'
|
||||
)
|
||||
pattern_depth : FloatProperty(
|
||||
name="Depth",
|
||||
default=0.02,
|
||||
min=0,
|
||||
soft_max=10,
|
||||
description="Displacement pattern depth"
|
||||
)
|
||||
pattern_offset : FloatProperty(
|
||||
name="Offset",
|
||||
default=0,
|
||||
soft_min=-1,
|
||||
soft_max=1,
|
||||
description="Displacement pattern offset"
|
||||
)
|
||||
pattern0 : IntProperty(
|
||||
name="Step 0",
|
||||
default=0,
|
||||
min=0,
|
||||
soft_max=10,
|
||||
description="Pattern step 0"
|
||||
)
|
||||
pattern1 : IntProperty(
|
||||
name="Step 1",
|
||||
default=0,
|
||||
min=0,
|
||||
soft_max=10,
|
||||
description="Pattern step 1"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
try:
|
||||
#bool_tessellated = context.object.tissue_tessellate.generator != None
|
||||
ob = context.object
|
||||
return ob.type in ('MESH','CURVE','SURFACE','FONT') and ob.mode == 'OBJECT'# and bool_tessellated
|
||||
except:
|
||||
return False
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.object = context.object.name
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
ob = context.object
|
||||
ob0 = bpy.data.objects[self.object]
|
||||
#props = ob.tissue_to_curve
|
||||
layout = self.layout
|
||||
col = layout.column(align=True)
|
||||
row = col.row(align=True)
|
||||
#row.label(text='Object: ' + self.object)
|
||||
#row.prop_search(self, "object", context.scene, "objects")
|
||||
#row.prop(self, "use_modifiers")#, icon='MODIFIER', text='')
|
||||
col.separator()
|
||||
col.label(text='Conversion Mode:')
|
||||
row = col.row(align=True)
|
||||
row.prop(
|
||||
self, "mode", text="Conversion Mode", icon='NONE', expand=True,
|
||||
slider=False, toggle=False, icon_only=False, event=False,
|
||||
full_event=False, emboss=True, index=-1)
|
||||
if self.mode == 'PARTICLES':
|
||||
col.separator()
|
||||
col.prop(self, "system")
|
||||
col.separator()
|
||||
if self.mode in ('LOOPS', 'EDGES'):
|
||||
row = col.row(align=True)
|
||||
row.prop(self, "use_modifiers")
|
||||
col2 = row.column(align=True)
|
||||
if self.use_modifiers:
|
||||
col2.prop(self, "subdivision_mode", text='', icon='NONE', expand=False,
|
||||
slider=True, toggle=False, icon_only=False, event=False,
|
||||
full_event=False, emboss=True, index=-1)
|
||||
col2.enabled = False
|
||||
for m in bpy.data.objects[self.object].modifiers:
|
||||
if m.type in ('SUBSURF','MULTIRES'): col2.enabled = True
|
||||
col.separator()
|
||||
row = col.row(align=True)
|
||||
row.label(text='Filter Edges:')
|
||||
col2 = row.column(align=True)
|
||||
col2.prop(self, "bounds_selection", text='', icon='NONE', expand=False,
|
||||
slider=True, toggle=False, icon_only=False, event=False,
|
||||
full_event=False, emboss=True, index=-1)
|
||||
col2.prop(self, 'only_sharp')
|
||||
col.separator()
|
||||
if self.mode == 'LOOPS':
|
||||
row = col.row(align=True)
|
||||
row.label(text='Filter Loops:')
|
||||
row.prop(self, "periodic_selection", text='', icon='NONE', expand=False,
|
||||
slider=True, toggle=False, icon_only=False, event=False,
|
||||
full_event=False, emboss=True, index=-1)
|
||||
col.separator()
|
||||
col.label(text='Spline Type:')
|
||||
row = col.row(align=True)
|
||||
row.prop(
|
||||
self, "spline_type", text="Spline Type", icon='NONE', expand=True,
|
||||
slider=False, toggle=False, icon_only=False, event=False,
|
||||
full_event=False, emboss=True, index=-1)
|
||||
if self.spline_type == 'NURBS':
|
||||
col.separator()
|
||||
col.label(text='Nurbs splines:')
|
||||
row = col.row(align=True)
|
||||
row.prop(self, "use_endpoint_u")
|
||||
row.prop(self, "nurbs_order")
|
||||
col.separator()
|
||||
col.prop(self, "bool_smooth")
|
||||
if ob0.type == 'MESH' and self.mode != 'PARTICLES':
|
||||
col.separator()
|
||||
col.label(text='Variable Radius:')
|
||||
row = col.row(align=True)
|
||||
row.prop_search(self, 'vertex_group', ob0, "vertex_groups", text='')
|
||||
row.prop(self, "invert_vertex_group", text="", toggle=True, icon='ARROW_LEFTRIGHT')
|
||||
row.prop(self, "vertex_group_factor")
|
||||
col.separator()
|
||||
col.label(text='Clean curves:')
|
||||
col.prop(self, "clean_distance")
|
||||
col.separator()
|
||||
col.label(text='Displacement Pattern:')
|
||||
row = col.row(align=True)
|
||||
row.prop(self, "pattern0")
|
||||
row.prop(self, "pattern1")
|
||||
row = col.row(align=True)
|
||||
row.prop(self, "pattern_depth")
|
||||
row.prop(self, "pattern_offset")
|
||||
|
||||
def execute(self, context):
|
||||
ob = context.active_object
|
||||
|
||||
crv = bpy.data.curves.new(ob.name + '_Curve', type='CURVE')
|
||||
crv.dimensions = '3D'
|
||||
new_ob = bpy.data.objects.new(ob.name + '_Curve', crv)
|
||||
bpy.context.collection.objects.link(new_ob)
|
||||
context.view_layer.objects.active = new_ob
|
||||
|
||||
new_ob.select_set(True)
|
||||
ob.select_set(False)
|
||||
new_ob.matrix_world = ob.matrix_world
|
||||
|
||||
new_ob.tissue.tissue_type = 'TO_CURVE'
|
||||
new_ob.tissue.bool_lock = True
|
||||
|
||||
props = new_ob.tissue_to_curve
|
||||
props.object = ob
|
||||
props.use_modifiers = self.use_modifiers
|
||||
props.subdivision_mode = self.subdivision_mode
|
||||
props.clean_distance = self.clean_distance
|
||||
props.spline_type = self.spline_type
|
||||
props.mode = self.mode
|
||||
props.use_endpoint_u = self.use_endpoint_u
|
||||
props.nurbs_order = self.nurbs_order
|
||||
props.vertex_group = self.vertex_group
|
||||
props.vertex_group_factor = self.vertex_group_factor
|
||||
props.invert_vertex_group = self.invert_vertex_group
|
||||
props.bool_smooth = self.bool_smooth
|
||||
props.system = self.system
|
||||
props.periodic_selection = self.periodic_selection
|
||||
props.bounds_selection = self.bounds_selection
|
||||
props.only_sharp = self.only_sharp
|
||||
props.pattern0 = self.pattern0
|
||||
props.pattern1 = self.pattern1
|
||||
props.pattern_depth = self.pattern_depth
|
||||
props.pattern_offset = self.pattern_offset
|
||||
|
||||
new_ob.tissue.bool_lock = False
|
||||
|
||||
bpy.ops.object.tissue_convert_to_curve_update()
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
class tissue_convert_to_curve_update(Operator):
|
||||
bl_idname = "object.tissue_convert_to_curve_update"
|
||||
bl_label = "Tissue Update Curve"
|
||||
bl_description = "Update Curve object"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
try:
|
||||
ob = context.object
|
||||
bool_curve = ob.tissue_to_curve.object != None
|
||||
return ob.type == 'CURVE' and ob.mode == 'OBJECT' and bool_curve
|
||||
except:
|
||||
return False
|
||||
|
||||
def execute(self, context):
|
||||
start_time = time.time()
|
||||
|
||||
ob = context.object
|
||||
props = ob.tissue_to_curve
|
||||
ob0 = props.object
|
||||
if props.mode == 'PARTICLES':
|
||||
eval_ob = ob0.evaluated_get(context.evaluated_depsgraph_get())
|
||||
system_id = min(props.system, len(eval_ob.particle_systems))
|
||||
psystem = eval_ob.particle_systems[system_id]
|
||||
ob.data.splines.clear()
|
||||
particles = psystem.particles
|
||||
for id,p in enumerate(particles):
|
||||
s = ob.data.splines.new('POLY')
|
||||
if psystem.settings.type == 'HAIR':
|
||||
n_pts = len(p.hair_keys)
|
||||
pts = [0]*3*n_pts
|
||||
p.hair_keys.foreach_get('co',pts)
|
||||
co = np.array(pts).reshape((-1,3))
|
||||
else:
|
||||
n_pts = 2**psystem.settings.display_step + 1
|
||||
pts = []
|
||||
for i in range(n_pts):
|
||||
vec = psystem.co_hair(eval_ob, particle_no=id,step=i)
|
||||
vec = ob0.matrix_world.inverted() @ vec
|
||||
pts.append(vec)
|
||||
co = np.array(pts)
|
||||
w = np.ones(n_pts).reshape((n_pts,1))
|
||||
co = np.concatenate((co,w),axis=1).reshape((n_pts*4))
|
||||
s.points.add(n_pts-1)
|
||||
s.points.foreach_set('co',co)
|
||||
|
||||
else:
|
||||
_ob0 = ob0
|
||||
ob0 = convert_object_to_mesh(ob0, apply_modifiers=props.use_modifiers)
|
||||
me = ob0.data
|
||||
n_verts = len(me.vertices)
|
||||
verts = [0]*n_verts*3
|
||||
me.vertices.foreach_get('co',verts)
|
||||
verts = np.array(verts).reshape((-1,3))
|
||||
|
||||
normals = [0]*n_verts*3
|
||||
me.vertices.foreach_get('normal',normals)
|
||||
normals = np.array(normals).reshape((-1,3))
|
||||
#tilt = np.degrees(np.arcsin(normals[:,2]))
|
||||
#tilt = np.arccos(normals[:,2])/2
|
||||
|
||||
verts = np.array(verts).reshape((-1,3))
|
||||
if props.mode in ('LOOPS','EDGES'):
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(me)
|
||||
bm.verts.ensure_lookup_table()
|
||||
bm.edges.ensure_lookup_table()
|
||||
bm.faces.ensure_lookup_table()
|
||||
todo_edges = list(bm.edges)
|
||||
if props.use_modifiers and props.subdivision_mode != 'ALL':
|
||||
me0, subs = get_mesh_before_subs(_ob0)
|
||||
n_edges0 = len(me0.edges)
|
||||
bpy.data.meshes.remove(me0)
|
||||
if props.subdivision_mode == 'CAGE':
|
||||
todo_edges = todo_edges[:n_edges0*(2**subs)]
|
||||
elif props.subdivision_mode == 'INNER':
|
||||
todo_edges = todo_edges[n_edges0*(2**subs):]
|
||||
|
||||
if props.only_sharp:
|
||||
_todo_edges = []
|
||||
sharp_verts = []
|
||||
for e in todo_edges:
|
||||
edge = me.edges[e.index]
|
||||
if edge.use_edge_sharp:
|
||||
_todo_edges.append(e)
|
||||
sharp_verts.append(edge.vertices[0])
|
||||
sharp_verts.append(edge.vertices[1])
|
||||
todo_edges = _todo_edges
|
||||
|
||||
if props.bounds_selection == 'BOUNDS': todo_edges = [e for e in todo_edges if len(e.link_faces)<2]
|
||||
elif props.bounds_selection == 'INNER': todo_edges = [e for e in todo_edges if len(e.link_faces)>1]
|
||||
|
||||
if props.mode == 'EDGES':
|
||||
ordered_points = [[e.verts[0].index, e.verts[1].index] for e in todo_edges]
|
||||
elif props.mode == 'LOOPS':
|
||||
vert_loops, edge_loops = loops_from_bmesh(todo_edges)
|
||||
if props.only_sharp:
|
||||
ordered_points = []
|
||||
for loop in vert_loops:
|
||||
loop_points = []
|
||||
for v in loop:
|
||||
if v.index in sharp_verts:
|
||||
loop_points.append(v.index)
|
||||
else:
|
||||
if len(loop_points)>1:
|
||||
ordered_points.append(loop_points)
|
||||
loop_points = []
|
||||
if len(loop_points)>1:
|
||||
ordered_points.append(loop_points)
|
||||
#ordered_points = [[v.index for v in loop if v.index in sharp_verts] for loop in vert_loops]
|
||||
else:
|
||||
ordered_points = [[v.index for v in loop] for loop in vert_loops]
|
||||
if props.periodic_selection == 'CLOSED':
|
||||
ordered_points = [points for points in ordered_points if points[0] == points[-1]]
|
||||
elif props.periodic_selection == 'OPEN':
|
||||
ordered_points = [points for points in ordered_points if points[0] != points[-1]]
|
||||
else:
|
||||
try:
|
||||
ordered_points = find_curves(edges, n_verts)
|
||||
except:
|
||||
bpy.data.objects.remove(ob0)
|
||||
return {'CANCELLED'}
|
||||
|
||||
try:
|
||||
weight = get_weight_numpy(ob0.vertex_groups[props.vertex_group], n_verts)
|
||||
if props.invert_vertex_group: weight = 1-weight
|
||||
fact = props.vertex_group_factor
|
||||
if fact > 0:
|
||||
weight = weight*(1-fact) + fact
|
||||
except:
|
||||
weight = None
|
||||
|
||||
# Set curves Tilt
|
||||
'''
|
||||
tilt = []
|
||||
for points in ordered_points:
|
||||
if points[0] == points[-1]: # Closed curve
|
||||
pts0 = [points[-1]] + points[:-1] # i-1
|
||||
pts1 = points[:] # i
|
||||
pts2 = points[1:] + [points[0]] # 1+1
|
||||
else: # Open curve
|
||||
pts0 = [points[0]] + points[:-1] # i-1
|
||||
pts1 = points[:] # i
|
||||
pts2 = points[1:] + [points[-1]] # i+1
|
||||
curve_tilt = []
|
||||
for i0, i1, i2 in zip(pts0, pts1, pts2):
|
||||
pt0 = Vector(verts[i0])
|
||||
pt1 = Vector(verts[i1])
|
||||
pt2 = Vector(verts[i2])
|
||||
tan1 = (pt1-pt0).normalized()
|
||||
tan2 = (pt2-pt1).normalized()
|
||||
vec_tan = -(tan1 + tan2).normalized()
|
||||
vec2 = vec_tan.cross(Vector((0,0,1)))
|
||||
vec_z = vec_tan.cross(vec2)
|
||||
nor = normals[i1]
|
||||
if vec_z.length == 0:
|
||||
vec_z = Vector(nor)
|
||||
ang = vec_z.angle(nor)
|
||||
if nor[2] < 0: ang = 2*pi-ang
|
||||
#if vec_tan[0] > vec_tan[1] and nor[0]>0: ang = -ang
|
||||
#if vec_tan[0] > vec_tan[2] and nor[0]>0: ang = -ang
|
||||
#if vec_tan[0] < vec_tan[1] and nor[1]>0: ang = -ang
|
||||
#if nor[0]*nor[1]*nor[2] < 0: ang = -ang
|
||||
if nor[2] == 0: ang = -5*pi/4
|
||||
#ang = max(ang, np.arccos(nor[2]))
|
||||
curve_tilt.append(ang)
|
||||
#curve_tilt.append(np.arccos(nor[2]))
|
||||
tilt.append(curve_tilt)
|
||||
'''
|
||||
depth = props.pattern_depth
|
||||
offset = props.pattern_offset
|
||||
pattern = [props.pattern0,props.pattern1]
|
||||
update_curve_from_pydata(ob.data, verts, normals, weight, ordered_points, merge_distance=props.clean_distance, pattern=pattern, depth=depth, offset=offset)
|
||||
|
||||
|
||||
bpy.data.objects.remove(ob0)
|
||||
for s in ob.data.splines:
|
||||
s.type = props.spline_type
|
||||
if s.type == 'NURBS':
|
||||
s.use_endpoint_u = props.use_endpoint_u
|
||||
s.order_u = props.nurbs_order
|
||||
ob.data.splines.update()
|
||||
if not props.bool_smooth: bpy.ops.object.shade_flat()
|
||||
|
||||
end_time = time.time()
|
||||
print('Tissue: object "{}" converted to Curve in {:.4f} sec'.format(ob.name, end_time-start_time))
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class TISSUE_PT_convert_to_curve(Panel):
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "data"
|
||||
bl_label = "Tissue Convert to Curve"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
try:
|
||||
#bool_curve = context.object.tissue_to_curve.object != None
|
||||
ob = context.object
|
||||
return ob.type == 'CURVE' and ob.tissue.tissue_type == 'TO_CURVE'
|
||||
except:
|
||||
return False
|
||||
|
||||
def draw(self, context):
|
||||
ob = context.object
|
||||
props = ob.tissue_to_curve
|
||||
|
||||
layout = self.layout
|
||||
#layout.use_property_split = True
|
||||
#layout.use_property_decorate = False
|
||||
col = layout.column(align=True)
|
||||
row = col.row(align=True)
|
||||
#col.operator("object.tissue_convert_to_curve_update", icon='FILE_REFRESH', text='Refresh')
|
||||
row.operator("object.tissue_update_tessellate_deps", icon='FILE_REFRESH', text='Refresh') ####
|
||||
lock_icon = 'LOCKED' if ob.tissue.bool_lock else 'UNLOCKED'
|
||||
#lock_icon = 'PINNED' if props.bool_lock else 'UNPINNED'
|
||||
deps_icon = 'LINKED' if ob.tissue.bool_dependencies else 'UNLINKED'
|
||||
row.prop(ob.tissue, "bool_dependencies", text="", icon=deps_icon)
|
||||
row.prop(ob.tissue, "bool_lock", text="", icon=lock_icon)
|
||||
col2 = row.column(align=True)
|
||||
col2.prop(ob.tissue, "bool_run", text="",icon='TIME')
|
||||
col2.enabled = not ob.tissue.bool_lock
|
||||
|
||||
col.separator()
|
||||
row = col.row(align=True)
|
||||
row.prop_search(props, "object", context.scene, "objects")
|
||||
row.prop(props, "use_modifiers", icon='MODIFIER', text='')
|
||||
col.separator()
|
||||
col.label(text='Conversion Mode:')
|
||||
row = col.row(align=True)
|
||||
row.prop(
|
||||
props, "mode", icon='NONE', expand=True,
|
||||
slider=False, toggle=False, icon_only=False, event=False,
|
||||
full_event=False, emboss=True, index=-1)
|
||||
if props.mode == 'PARTICLES':
|
||||
col.separator()
|
||||
col.prop(props, "system")
|
||||
col.separator()
|
||||
|
||||
if props.mode in ('LOOPS', 'EDGES'):
|
||||
row = col.row(align=True)
|
||||
row.prop(props, "use_modifiers")
|
||||
col2 = row.column(align=True)
|
||||
if props.use_modifiers:
|
||||
col2.prop(props, "subdivision_mode", text='', icon='NONE', expand=False,
|
||||
slider=True, toggle=False, icon_only=False, event=False,
|
||||
full_event=False, emboss=True, index=-1)
|
||||
col2.enabled = False
|
||||
for m in props.object.modifiers:
|
||||
if m.type in ('SUBSURF','MULTIRES'): col2.enabled = True
|
||||
col.separator()
|
||||
row = col.row(align=True)
|
||||
row.label(text='Filter Edges:')
|
||||
col2 = row.column(align=True)
|
||||
col2.prop(props, "bounds_selection", text='', icon='NONE', expand=False,
|
||||
slider=True, toggle=False, icon_only=False, event=False,
|
||||
full_event=False, emboss=True, index=-1)
|
||||
col2.prop(props, 'only_sharp')
|
||||
col.separator()
|
||||
if props.mode == 'LOOPS':
|
||||
row = col.row(align=True)
|
||||
row.label(text='Filter Loops:')
|
||||
row.prop(props, "periodic_selection", text='', icon='NONE', expand=False,
|
||||
slider=True, toggle=False, icon_only=False, event=False,
|
||||
full_event=False, emboss=True, index=-1)
|
||||
col.separator()
|
||||
|
||||
col.label(text='Spline Type:')
|
||||
row = col.row(align=True)
|
||||
row.prop(
|
||||
props, "spline_type", text="Spline Type", icon='NONE', expand=True,
|
||||
slider=False, toggle=False, icon_only=False, event=False,
|
||||
full_event=False, emboss=True, index=-1)
|
||||
if props.spline_type == 'NURBS':
|
||||
col.separator()
|
||||
col.label(text='Nurbs Splines:')
|
||||
row = col.row(align=True)
|
||||
row.prop(props, "use_endpoint_u")
|
||||
row.prop(props, "nurbs_order")
|
||||
col.separator()
|
||||
col.prop(props, "bool_smooth")
|
||||
if props.object.type == 'MESH':
|
||||
col.separator()
|
||||
col.label(text='Variable Radius:')
|
||||
row = col.row(align=True)
|
||||
row.prop_search(props, 'vertex_group', props.object, "vertex_groups", text='')
|
||||
row.prop(props, "invert_vertex_group", text="", toggle=True, icon='ARROW_LEFTRIGHT')
|
||||
row.prop(props, "vertex_group_factor")
|
||||
col.separator()
|
||||
col.label(text='Clean Curves:')
|
||||
col.prop(props, "clean_distance")
|
||||
col.separator()
|
||||
col.label(text='Displacement Pattern:')
|
||||
row = col.row(align=True)
|
||||
row.prop(props, "pattern0")
|
||||
row.prop(props, "pattern1")
|
||||
row = col.row(align=True)
|
||||
row.prop(props, "pattern_depth")
|
||||
row.prop(props, "pattern_offset")
|
|
@ -41,29 +41,40 @@ class dual_mesh_tessellated(Operator):
|
|||
('QUAD', 'Quad Faces', ''),
|
||||
('TRI', 'Triangles', '')],
|
||||
name="Source Faces",
|
||||
description="Source polygons",
|
||||
default="QUAD",
|
||||
description="Triangles works with any geometry." \
|
||||
"Quad option is faster when the object has only Quads",
|
||||
default="TRI",
|
||||
options={'LIBRARY_EDITABLE'}
|
||||
)
|
||||
|
||||
link_component : BoolProperty(
|
||||
name="Editable Component",
|
||||
default=False,
|
||||
description="Add Component Object to the Scene"
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
auto_layer_collection()
|
||||
ob0 = context.object
|
||||
name1 = "DualMesh_{}_Component".format(self.source_faces)
|
||||
# Generate component
|
||||
if self.source_faces == 'QUAD':
|
||||
verts = [(0.0, 0.0, 0.0), (0.0, 0.5, 0.0),
|
||||
verts = [(1.0, 0.0, 0.0), (0.5, 0.0, 0.0),
|
||||
(0.0, 0.0, 0.0), (0.0, 0.5, 0.0),
|
||||
(0.0, 1.0, 0.0), (0.5, 1.0, 0.0),
|
||||
(1.0, 1.0, 0.0), (1.0, 0.5, 0.0),
|
||||
(1.0, 0.0, 0.0), (0.5, 0.0, 0.0),
|
||||
(1/3, 1/3, 0.0), (2/3, 2/3, 0.0)]
|
||||
(2/3, 1/3, 0.0), (1/3, 2/3, 0.0)]
|
||||
edges = [(0,1), (1,2), (2,3), (3,4), (4,5), (5,6), (6,7),
|
||||
(7,0), (1,8), (8,7), (3,9), (9,5), (8,9)]
|
||||
faces = [(7,8,1,0), (8,9,3,2,1), (9,5,4,3), (9,8,7,6,5)]
|
||||
else:
|
||||
verts = [(0.0,0.0,0.0), (0.5,0.0,0.0), (1.0,0.0,0.0), (0.0,1.0,0.0), (0.5,1.0,0.0), (1.0,1.0,0.0)]
|
||||
edges = [(0,1), (1,2), (2,5), (5,4), (4,3), (3,0), (1,4)]
|
||||
faces = [(0,1,4,3), (1,2,5,4)]
|
||||
verts = [(0.0, 0.0, 0.0), (1.0, 0.0, 0.0),
|
||||
(0.0, 1.0, 0.0), (1.0, 1.0, 0.0),
|
||||
(0.5, 1/3, 0.0), (0.0, 0.5, 0.0),
|
||||
(1.0, 0.5, 0.0), (0.5, 0.0, 0.0)]
|
||||
edges = [(0,5), (1,7), (3,6), (2,3), (2,5), (1,6), (0,7),
|
||||
(4,5), (4,7), (4,6)]
|
||||
faces = [(5,0,7,4), (7,1,6,4), (3,2,5,4,6)]
|
||||
|
||||
# check pre-existing component
|
||||
try:
|
||||
|
@ -78,38 +89,38 @@ class dual_mesh_tessellated(Operator):
|
|||
me = bpy.data.meshes.new("Dual-Mesh") # add a new mesh
|
||||
me.from_pydata(verts, edges, faces)
|
||||
me.update(calc_edges=True, calc_edges_loose=True)
|
||||
if self.source_faces == 'QUAD': n_seams = 8
|
||||
else: n_seams = 6
|
||||
for i in range(n_seams): me.edges[i].use_seam = True
|
||||
if self.source_faces == 'QUAD': seams = (0,1,2,3,4,5,6,9)
|
||||
else: seams = (0,1,2,3,4,5,7)
|
||||
for i in seams: me.edges[i].use_seam = True
|
||||
ob1 = bpy.data.objects.new(name1, me)
|
||||
bpy.context.collection.objects.link(ob1)
|
||||
# fix visualization issue
|
||||
bpy.context.view_layer.objects.active = ob1
|
||||
ob1.select_set(True)
|
||||
bpy.ops.object.editmode_toggle()
|
||||
bpy.ops.object.editmode_toggle()
|
||||
ob1.select_set(False)
|
||||
# hide component
|
||||
ob1.hide_select = True
|
||||
ob1.hide_render = True
|
||||
ob1.hide_viewport = True
|
||||
if self.link_component:
|
||||
context.collection.objects.link(ob1)
|
||||
context.view_layer.objects.active = ob1
|
||||
ob1.select_set(True)
|
||||
bpy.ops.object.editmode_toggle()
|
||||
bpy.ops.object.editmode_toggle()
|
||||
ob1.select_set(False)
|
||||
ob1.hide_render = True
|
||||
ob = convert_object_to_mesh(ob0,False,False)
|
||||
ob.name = 'DualMesh'
|
||||
#ob = bpy.data.objects.new("DualMesh", convert_object_to_mesh(ob0,False,False))
|
||||
#bpy.context.collection.objects.link(ob)
|
||||
#bpy.context.view_layer.objects.active = ob
|
||||
#ob.select_set(True)
|
||||
ob.tissue.tissue_type = 'TESSELLATE'
|
||||
ob.tissue.bool_lock = True
|
||||
ob.tissue_tessellate.component = ob1
|
||||
ob.tissue_tessellate.generator = ob0
|
||||
ob.tissue_tessellate.gen_modifiers = self.apply_modifiers
|
||||
ob.tissue_tessellate.merge = True
|
||||
ob.tissue_tessellate.bool_dissolve_seams = True
|
||||
if self.source_faces == 'TRI': ob.tissue_tessellate.fill_mode = 'FAN'
|
||||
bpy.ops.object.update_tessellate()
|
||||
if self.source_faces == 'TRI': ob.tissue_tessellate.fill_mode = 'TRI'
|
||||
bpy.ops.object.tissue_update_tessellate()
|
||||
ob.tissue.bool_lock = False
|
||||
ob.location = ob0.location
|
||||
ob.matrix_world = ob0.matrix_world
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
class dual_mesh(Operator):
|
||||
bl_idname = "object.dual_mesh"
|
||||
bl_label = "Convert to Dual Mesh"
|
||||
|
@ -135,9 +146,9 @@ class dual_mesh(Operator):
|
|||
items=[
|
||||
('BEAUTY', 'Beauty', 'Arrange the new triangles evenly'),
|
||||
('CLIP', 'Clip',
|
||||
'Split the polygons with an ear clipping algorithm')],
|
||||
name="Polygon Method",
|
||||
description="Method for splitting the polygons into triangles",
|
||||
'Split the N-gon with an ear clipping algorithm')],
|
||||
name="N-gon Method",
|
||||
description="Method for splitting the N-gons into triangles",
|
||||
default="BEAUTY",
|
||||
options={'LIBRARY_EDITABLE'}
|
||||
)
|
||||
|
@ -156,12 +167,12 @@ class dual_mesh(Operator):
|
|||
mode = context.mode
|
||||
if mode == 'EDIT_MESH':
|
||||
mode = 'EDIT'
|
||||
act = bpy.context.active_object
|
||||
act = context.active_object
|
||||
if mode != 'OBJECT':
|
||||
sel = [act]
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
else:
|
||||
sel = bpy.context.selected_objects
|
||||
sel = context.selected_objects
|
||||
doneMeshes = []
|
||||
|
||||
for ob0 in sel:
|
||||
|
@ -190,7 +201,7 @@ class dual_mesh(Operator):
|
|||
ob.data = ob.data.copy()
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
ob.select_set(True)
|
||||
bpy.context.view_layer.objects.active = ob0
|
||||
context.view_layer.objects.active = ob0
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
# prevent borders erosion
|
||||
|
@ -256,23 +267,23 @@ class dual_mesh(Operator):
|
|||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
# select quad faces
|
||||
bpy.context.tool_settings.mesh_select_mode = (False, False, True)
|
||||
context.tool_settings.mesh_select_mode = (False, False, True)
|
||||
bpy.ops.mesh.select_face_by_sides(number=4, extend=False)
|
||||
|
||||
# deselect boundaries
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
for i in bound_v:
|
||||
bpy.context.active_object.data.vertices[i].select = False
|
||||
context.active_object.data.vertices[i].select = False
|
||||
for i in bound_e:
|
||||
bpy.context.active_object.data.edges[i].select = False
|
||||
context.active_object.data.edges[i].select = False
|
||||
for i in bound_p:
|
||||
bpy.context.active_object.data.polygons[i].select = False
|
||||
context.active_object.data.polygons[i].select = False
|
||||
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
bpy.context.tool_settings.mesh_select_mode = (False, False, True)
|
||||
context.tool_settings.mesh_select_mode = (False, False, True)
|
||||
bpy.ops.mesh.edge_face_add()
|
||||
bpy.context.tool_settings.mesh_select_mode = (True, False, False)
|
||||
context.tool_settings.mesh_select_mode = (True, False, False)
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
|
||||
# delete boundaries
|
||||
|
@ -314,11 +325,12 @@ class dual_mesh(Operator):
|
|||
|
||||
for o in clones:
|
||||
o.data = ob.data
|
||||
bm.free()
|
||||
|
||||
for o in sel:
|
||||
o.select_set(True)
|
||||
|
||||
bpy.context.view_layer.objects.active = act
|
||||
context.view_layer.objects.active = act
|
||||
bpy.ops.object.mode_set(mode=mode)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
|
|
@ -132,7 +132,7 @@ def grid_from_mesh(mesh, swap_uv):
|
|||
|
||||
if len(faces_loop) == 0:
|
||||
running_grid = False
|
||||
|
||||
bm.free()
|
||||
return verts_grid, edges_grid, faces_grid
|
||||
|
||||
|
||||
|
@ -225,12 +225,20 @@ class lattice_along_surface(Operator):
|
|||
soft_max=1,
|
||||
description="Lattice displace"
|
||||
)
|
||||
weight_factor : FloatProperty(
|
||||
name="Factor",
|
||||
default=0,
|
||||
min=0.000,
|
||||
max=1.000,
|
||||
precision=3,
|
||||
description="Thickness factor to use for zero vertex group influence"
|
||||
)
|
||||
grid_object = ""
|
||||
source_object = ""
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
try: return bpy.context.object.mode == 'OBJECT'
|
||||
try: return context.object.mode == 'OBJECT'
|
||||
except: return False
|
||||
|
||||
def draw(self, context):
|
||||
|
@ -249,6 +257,9 @@ class lattice_along_surface(Operator):
|
|||
)
|
||||
row = col.row()
|
||||
row.prop(self, "use_groups")
|
||||
if self.use_groups:
|
||||
row = col.row()
|
||||
row.prop(self, "weight_factor")
|
||||
col.separator()
|
||||
col.label(text="Scale:")
|
||||
col.prop(
|
||||
|
@ -277,16 +288,16 @@ class lattice_along_surface(Operator):
|
|||
|
||||
def execute(self, context):
|
||||
if self.source_object == self.grid_object == "" or True:
|
||||
if len(bpy.context.selected_objects) != 2:
|
||||
if len(context.selected_objects) != 2:
|
||||
self.report({'ERROR'}, "Please, select two objects")
|
||||
return {'CANCELLED'}
|
||||
grid_obj = bpy.context.object
|
||||
grid_obj = context.object
|
||||
if grid_obj.type not in ('MESH', 'CURVE', 'SURFACE'):
|
||||
self.report({'ERROR'}, "The surface object is not valid. Only Mesh,"
|
||||
"Curve and Surface objects are allowed.")
|
||||
return {'CANCELLED'}
|
||||
obj = None
|
||||
for o in bpy.context.selected_objects:
|
||||
for o in context.selected_objects:
|
||||
if o.name != grid_obj.name and o.type in \
|
||||
('MESH', 'CURVE', 'SURFACE', 'FONT'):
|
||||
obj = o
|
||||
|
@ -305,9 +316,9 @@ class lattice_along_surface(Operator):
|
|||
grid_obj = bpy.data.objects[self.grid_object]
|
||||
obj = bpy.data.objects[self.source_object]
|
||||
obj_me = simple_to_mesh(obj)# obj.to_mesh(bpy.context.depsgraph, apply_modifiers=True)
|
||||
for o in bpy.context.selected_objects: o.select_set(False)
|
||||
for o in context.selected_objects: o.select_set(False)
|
||||
grid_obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = grid_obj
|
||||
context.view_layer.objects.active = grid_obj
|
||||
|
||||
temp_grid_obj = grid_obj.copy()
|
||||
temp_grid_obj.data = simple_to_mesh(grid_obj)
|
||||
|
@ -318,7 +329,7 @@ class lattice_along_surface(Operator):
|
|||
|
||||
if len(grid_mesh.polygons) > 64 * 64:
|
||||
bpy.data.objects.remove(temp_grid_obj)
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
self.report({'ERROR'}, "Maximum resolution allowed for Lattice is 64")
|
||||
return {'CANCELLED'}
|
||||
|
@ -347,7 +358,7 @@ class lattice_along_surface(Operator):
|
|||
bb = max - min
|
||||
lattice_loc = (max + min) / 2
|
||||
bpy.ops.object.add(type='LATTICE')
|
||||
lattice = bpy.context.active_object
|
||||
lattice = context.active_object
|
||||
lattice.location = lattice_loc
|
||||
lattice.scale = Vector((bb.x / self.scale_x, bb.y / self.scale_y,
|
||||
bb.z / self.scale_z))
|
||||
|
@ -359,16 +370,14 @@ class lattice_along_surface(Operator):
|
|||
if bb.z == 0:
|
||||
lattice.scale.z = 1
|
||||
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
context.view_layer.objects.active = obj
|
||||
bpy.ops.object.modifier_add(type='LATTICE')
|
||||
obj.modifiers[-1].object = lattice
|
||||
|
||||
# set as parent
|
||||
if self.set_parent:
|
||||
obj.select_set(True)
|
||||
lattice.select_set(True)
|
||||
bpy.context.view_layer.objects.active = lattice
|
||||
bpy.ops.object.parent_set(type='LATTICE')
|
||||
override = {'active_object': obj, 'selected_objects' : [lattice,obj]}
|
||||
bpy.ops.object.parent_set(override, type='OBJECT', keep_transform=False)
|
||||
|
||||
# reading grid structure
|
||||
verts_grid, edges_grid, faces_grid = grid_from_mesh(
|
||||
|
@ -384,15 +393,19 @@ class lattice_along_surface(Operator):
|
|||
lattice.data.points_u = nu
|
||||
lattice.data.points_v = nv
|
||||
lattice.data.points_w = nw
|
||||
if self.use_groups:
|
||||
vg = temp_grid_obj.vertex_groups.active
|
||||
weight_factor = self.weight_factor
|
||||
for i in range(nu):
|
||||
for j in range(nv):
|
||||
for w in range(nw):
|
||||
if self.use_groups:
|
||||
try:
|
||||
displace = temp_grid_obj.vertex_groups.active.weight(
|
||||
verts_grid[i][j]) * scale_normal * bb.z
|
||||
weight_influence = vg.weight(verts_grid[i][j])
|
||||
except:
|
||||
displace = 0#scale_normal * bb.z
|
||||
weight_influence = 0
|
||||
weight_influence = weight_influence * (1 - weight_factor) + weight_factor
|
||||
displace = weight_influence * scale_normal * bb.z
|
||||
else:
|
||||
displace = scale_normal * bb.z
|
||||
target_point = (grid_mesh.vertices[verts_grid[i][j]].co +
|
||||
|
@ -418,7 +431,7 @@ class lattice_along_surface(Operator):
|
|||
lattice.select_set(True)
|
||||
obj.select_set(False)
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
bpy.ops.object.modifier_remove(modifier=obj.modifiers[-1].name)
|
||||
if nu > 64 or nv > 64:
|
||||
|
@ -433,18 +446,18 @@ class lattice_along_surface(Operator):
|
|||
#lattice.select_set(False)
|
||||
obj.select_set(False)
|
||||
#bpy.ops.object.delete(use_global=False)
|
||||
bpy.context.view_layer.objects.active = lattice
|
||||
context.view_layer.objects.active = lattice
|
||||
lattice.select_set(True)
|
||||
|
||||
if self.high_quality_lattice:
|
||||
bpy.context.object.data.points_w = 8
|
||||
context.object.data.points_w = 8
|
||||
else:
|
||||
bpy.context.object.data.use_outside = True
|
||||
context.object.data.use_outside = True
|
||||
|
||||
if self.hide_lattice:
|
||||
bpy.ops.object.hide_view_set(unselected=False)
|
||||
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
lattice.select_set(False)
|
||||
|
||||
|
|
|
@ -0,0 +1,231 @@
|
|||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# #
|
||||
# (c) Alessandro Zomparelli #
|
||||
# (2020) #
|
||||
# #
|
||||
# http://www.co-de-it.com/ #
|
||||
# #
|
||||
################################################################################
|
||||
|
||||
import bpy
|
||||
import numpy as np
|
||||
|
||||
import colorsys
|
||||
from numpy import *
|
||||
|
||||
from bpy.types import (
|
||||
Operator,
|
||||
Panel
|
||||
)
|
||||
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
EnumProperty,
|
||||
FloatProperty,
|
||||
IntProperty,
|
||||
StringProperty,
|
||||
FloatVectorProperty,
|
||||
IntVectorProperty
|
||||
)
|
||||
|
||||
from .utils import *
|
||||
|
||||
class random_materials(Operator):
|
||||
bl_idname = "object.random_materials"
|
||||
bl_label = "Random Materials"
|
||||
bl_description = "Assign random materials to the faces of the mesh"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
prefix : StringProperty(
|
||||
name="Prefix", default="Random.", description="Name prefix")
|
||||
|
||||
color_A : FloatVectorProperty(name="Color A",
|
||||
subtype='COLOR_GAMMA',
|
||||
min=0,
|
||||
max=1,
|
||||
default=[0,0,0])
|
||||
|
||||
color_B : FloatVectorProperty(name="Color B",
|
||||
subtype='COLOR_GAMMA',
|
||||
min=0,
|
||||
max=1,
|
||||
default=[1,1,1])
|
||||
|
||||
hue : FloatProperty(name="Hue", min=0, max=1, default=0.5)
|
||||
hue_variation : FloatProperty(name="Hue Variation", min=0, max=1, default=0.6)
|
||||
|
||||
seed : IntProperty(
|
||||
name="Seed", default=0, description="Random seed")
|
||||
|
||||
count : IntProperty(
|
||||
name="Count", default=3, min=2, description="Count of random materials")
|
||||
|
||||
generate_materials : BoolProperty(
|
||||
name="Generate Materials", default=False, description="Automatically generates new materials")
|
||||
|
||||
random_colors : BoolProperty(
|
||||
name="Random Colors", default=True, description="Colors are automatically generated")
|
||||
|
||||
executed = False
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
try: return context.object.type == 'MESH'
|
||||
except: return False
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
col = layout.column(align=True)
|
||||
col.prop(self, "seed")
|
||||
col.prop(self, "generate_materials")
|
||||
if self.generate_materials:
|
||||
col.prop(self, "prefix")
|
||||
col.separator()
|
||||
col.prop(self, "count")
|
||||
#row = col.row(align=True)
|
||||
col.separator()
|
||||
col.label(text='Colors:')
|
||||
col.prop(self, "hue")
|
||||
col.prop(self, "hue_variation")
|
||||
#col.prop(self, "random_colors")
|
||||
if not self.random_colors:
|
||||
col.prop(self, "color_A")
|
||||
col.prop(self, "color_B")
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
ob = context.active_object
|
||||
if len(ob.material_slots) == 0 and not self.executed:
|
||||
self.generate_materials = True
|
||||
if self.generate_materials:
|
||||
colA = self.color_A
|
||||
colB = self.color_B
|
||||
h1 = (self.hue - self.hue_variation/2)
|
||||
h2 = (self.hue + self.hue_variation/2)
|
||||
count = self.count
|
||||
ob.data.materials.clear()
|
||||
materials = []
|
||||
for i in range(count):
|
||||
mat_name = '{}{:03d}'.format(self.prefix,i)
|
||||
mat = bpy.data.materials.new(mat_name)
|
||||
if self.random_colors:
|
||||
mat.diffuse_color = colorsys.hsv_to_rgb((h1 + (h2-h1)/(count)*i)%1, 1, 1)[:] + (1,)
|
||||
else:
|
||||
mat.diffuse_color = list(colA + (colB - colA)/(count-1)*i) + [1]
|
||||
ob.data.materials.append(mat)
|
||||
else:
|
||||
count = len(ob.material_slots)
|
||||
np.random.seed(seed=self.seed)
|
||||
n_faces = len(ob.data.polygons)
|
||||
if count > 0:
|
||||
rand = list(np.random.randint(count, size=n_faces))
|
||||
ob.data.polygons.foreach_set('material_index',rand)
|
||||
ob.data.update()
|
||||
self.executed = True
|
||||
return {'FINISHED'}
|
||||
|
||||
class weight_to_materials(Operator):
|
||||
bl_idname = "object.weight_to_materials"
|
||||
bl_label = "Weight to Materials"
|
||||
bl_description = "Assign materials to the faces of the mesh according to the active Vertex Group"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
prefix : StringProperty(
|
||||
name="Prefix", default="Weight.", description="Name prefix")
|
||||
|
||||
hue : FloatProperty(name="Hue", min=0, max=1, default=0.5)
|
||||
hue_variation : FloatProperty(name="Hue Variation", min=0, max=1, default=0.3)
|
||||
|
||||
count : IntProperty(
|
||||
name="Count", default=3, min=2, description="Count of random materials")
|
||||
|
||||
generate_materials : BoolProperty(
|
||||
name="Generate Materials", default=False, description="Automatically generates new materials")
|
||||
|
||||
mode : EnumProperty(
|
||||
items=(
|
||||
('MIN', "Min", "Use the min weight value"),
|
||||
('MAX', "Max", "Use the max weight value"),
|
||||
('MEAN', "Mean", "Use the mean weight value")
|
||||
),
|
||||
default='MEAN',
|
||||
name="Mode"
|
||||
)
|
||||
|
||||
vg = None
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
try: return context.object.type == 'MESH'
|
||||
except: return False
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
col = layout.column(align=True)
|
||||
col.prop(self, "mode")
|
||||
col.prop(self, "generate_materials")
|
||||
if self.generate_materials:
|
||||
col.prop(self, "prefix")
|
||||
col.separator()
|
||||
col.prop(self, "count")
|
||||
#row = col.row(align=True)
|
||||
col.separator()
|
||||
col.label(text='Colors:')
|
||||
col.prop(self, "hue")
|
||||
col.prop(self, "hue_variation")
|
||||
|
||||
def execute(self, context):
|
||||
ob = context.active_object
|
||||
if self.vg == None:
|
||||
self.vg = ob.vertex_groups.active_index
|
||||
vg = ob.vertex_groups[self.vg]
|
||||
if vg == None:
|
||||
self.report({'ERROR'}, "The selected object doesn't have any Vertex Group")
|
||||
return {'CANCELLED'}
|
||||
weight = get_weight_numpy(vg, len(ob.data.vertices))
|
||||
if self.generate_materials:
|
||||
h1 = (self.hue - self.hue_variation/2)
|
||||
h2 = (self.hue + self.hue_variation/2)
|
||||
count = self.count
|
||||
ob.data.materials.clear()
|
||||
materials = []
|
||||
for i in range(count):
|
||||
mat_name = '{}{:03d}'.format(self.prefix,i)
|
||||
mat = bpy.data.materials.new(mat_name)
|
||||
mat.diffuse_color = colorsys.hsv_to_rgb((h1 + (h2-h1)/(count)*i)%1, 1, 1)[:] + (1,)
|
||||
ob.data.materials.append(mat)
|
||||
else:
|
||||
count = len(ob.material_slots)
|
||||
|
||||
faces_weight = []
|
||||
for p in ob.data.polygons:
|
||||
verts_id = np.array([v for v in p.vertices])
|
||||
face_weight = weight[verts_id]
|
||||
if self.mode == 'MIN': w = face_weight.min()
|
||||
if self.mode == 'MAX': w = face_weight.max()
|
||||
if self.mode == 'MEAN': w = face_weight.mean()
|
||||
faces_weight.append(w)
|
||||
faces_weight = np.array(faces_weight)
|
||||
faces_weight = faces_weight * count
|
||||
faces_weight.astype('int')
|
||||
ob.data.polygons.foreach_set('material_index',list(faces_weight))
|
||||
ob.data.update()
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
return {'FINISHED'}
|
|
@ -1,19 +1,196 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import numpy as np
|
||||
try:
|
||||
from numba import jit
|
||||
import time
|
||||
import sys
|
||||
|
||||
bool_numba = False
|
||||
|
||||
try:
|
||||
from .utils_pip import Pip
|
||||
Pip._ensure_user_site_package()
|
||||
from numba import jit, njit, guvectorize, float64, int32, prange
|
||||
from numba.typed import List
|
||||
bool_numba = True
|
||||
except:
|
||||
pass
|
||||
'''
|
||||
try:
|
||||
from .utils_pip import Pip
|
||||
#Pip.upgrade_pip()
|
||||
Pip.install('llvmlite')
|
||||
Pip.install('numba')
|
||||
from numba import jit, njit, guvectorize, float64, int32, prange
|
||||
bool_numba = True
|
||||
print('Tissue: Numba successfully installed!')
|
||||
except:
|
||||
print('Tissue: Numba not loaded correctly. Try restarting Blender')
|
||||
'''
|
||||
|
||||
if bool_numba:
|
||||
#from numba import jit, njit, guvectorize, float64, int32, prange
|
||||
|
||||
@njit(parallel=True)
|
||||
def numba_reaction_diffusion(n_verts, n_edges, edge_verts, a, b, brush, diff_a, diff_b, f, k, dt, time_steps):
|
||||
arr = np.arange(n_edges)*2
|
||||
id0 = edge_verts[arr]
|
||||
id1 = edge_verts[arr+1]
|
||||
for i in range(time_steps):
|
||||
lap_a, lap_b = rd_init_laplacian(n_verts)
|
||||
numba_rd_laplacian(id0, id1, a, b, lap_a, lap_b)
|
||||
numba_rd_core(a, b, lap_a, lap_b, diff_a, diff_b, f, k, dt)
|
||||
numba_set_ab(a,b,brush)
|
||||
return a,b
|
||||
|
||||
@njit(parallel=False)
|
||||
def integrate_field(n_edges, id0, id1, values, edge_flow, mult, time_steps):
|
||||
#n_edges = len(edge_flow)
|
||||
for i in range(time_steps):
|
||||
values0 = values
|
||||
for j in range(n_edges):
|
||||
v0 = id0[j]
|
||||
v1 = id1[j]
|
||||
values[v0] -= values0[v1] * edge_flow[j] * 0.001#mult[v1]
|
||||
values[v1] += values0[v0] * edge_flow[j] * 0.001#mult[v0]
|
||||
for j in range(n_edges):
|
||||
v0 = id0[j]
|
||||
v1 = id1[j]
|
||||
values[v0] = max(values[v0],0)
|
||||
values[v1] = max(values[v1],0)
|
||||
return values
|
||||
|
||||
@njit(parallel=True)
|
||||
def numba_reaction_diffusion_anisotropic(n_verts, n_edges, edge_verts, a, b, brush, diff_a, diff_b, f, k, dt, time_steps, grad):
|
||||
arr = np.arange(n_edges)*2
|
||||
id0 = edge_verts[arr]
|
||||
id1 = edge_verts[arr+1]
|
||||
#grad = weight_grad[id0] - weight_grad[id1]
|
||||
#grad = np.abs(grad)
|
||||
#grad /= abs(np.max(grad))
|
||||
#grad = grad*0.98 + 0.02
|
||||
for i in range(time_steps):
|
||||
lap_a, lap_b = rd_init_laplacian(n_verts)
|
||||
numba_rd_laplacian_anisotropic(id0, id1, a, b, lap_a, lap_b, grad)
|
||||
numba_rd_core(a, b, lap_a, lap_b, diff_a, diff_b, f, k, dt)
|
||||
numba_set_ab(a,b,brush)
|
||||
return a,b
|
||||
|
||||
#@guvectorize(['(float64[:] ,float64[:] , float64[:], float64[:], float64[:], float64[:], float64[:], float64[:], float64)'],'(n),(n),(n),(n),(n),(n),(n),(n),()',target='parallel')
|
||||
@njit(parallel=True)
|
||||
def numba_rd_core(a, b, lap_a, lap_b, diff_a, diff_b, f, k, dt):
|
||||
n = len(a)
|
||||
_f = np.full(n, f[0]) if len(f) == 1 else f
|
||||
_k = np.full(n, k[0]) if len(k) == 1 else k
|
||||
_diff_a = np.full(n, diff_a[0]) if len(diff_a) == 1 else diff_a
|
||||
_diff_b = np.full(n, diff_b[0]) if len(diff_b) == 1 else diff_b
|
||||
|
||||
for i in prange(n):
|
||||
fi = _f[i]
|
||||
ki = _k[i]
|
||||
diff_ai = _diff_a[i]
|
||||
diff_bi = _diff_b[i]
|
||||
ab2 = a[i]*b[i]**2
|
||||
a[i] += (diff_ai * lap_a[i] - ab2 + fi*(1-a[i]))*dt
|
||||
b[i] += (diff_bi * lap_b[i] + ab2 - (ki+fi)*b[i])*dt
|
||||
|
||||
@njit(parallel=True)
|
||||
def numba_rd_core_(a, b, lap_a, lap_b, diff_a, diff_b, f, k, dt):
|
||||
ab2 = a*b**2
|
||||
a += (diff_a*lap_a - ab2 + f*(1-a))*dt
|
||||
b += (diff_b*lap_b + ab2 - (k+f)*b)*dt
|
||||
|
||||
@njit(parallel=True)
|
||||
def numba_set_ab(a, b, brush):
|
||||
n = len(a)
|
||||
_brush = np.full(n, brush[0]) if len(brush) == 1 else brush
|
||||
for i in prange(len(b)):
|
||||
b[i] += _brush[i]
|
||||
if b[i] < 0: b[i] = 0
|
||||
elif b[i] > 1: b[i] = 1
|
||||
if a[i] < 0: a[i] = 0
|
||||
elif a[i] > 1: a[i] = 1
|
||||
|
||||
|
||||
#@guvectorize(['(float64[:] ,float64[:] ,float64[:] , float64[:], float64[:], float64[:])'],'(m),(m),(n),(n),(n),(n)',target='parallel')
|
||||
@njit(parallel=True)
|
||||
def numba_rd_laplacian(id0, id1, a, b, lap_a, lap_b):
|
||||
for i in prange(len(id0)):
|
||||
v0 = id0[i]
|
||||
v1 = id1[i]
|
||||
lap_a[v0] += a[v1] - a[v0]
|
||||
lap_a[v1] += a[v0] - a[v1]
|
||||
lap_b[v0] += b[v1] - b[v0]
|
||||
lap_b[v1] += b[v0] - b[v1]
|
||||
#return lap_a, lap_b
|
||||
|
||||
@njit(parallel=True)
|
||||
def numba_rd_laplacian_anisotropic(id0, id1, a, b, lap_a, lap_b, grad):
|
||||
for i in prange(len(id0)):
|
||||
v0 = id0[i]
|
||||
v1 = id1[i]
|
||||
lap_a[v0] += (a[v1] - a[v0])
|
||||
lap_a[v1] += (a[v0] - a[v1])
|
||||
lap_b[v0] -= (b[v1] - b[v0])*grad[i]
|
||||
lap_b[v1] += (b[v0] - b[v1])*grad[i]
|
||||
#return lap_a, lap_b
|
||||
|
||||
@njit(parallel=True)
|
||||
def numba_rd_neigh_vertices(edge_verts):
|
||||
n_edges = len(edge_verts)/2
|
||||
id0 = np.zeros(n_edges)
|
||||
id1 = np.zeros(n_edges)
|
||||
for i in prange(n_edges):
|
||||
id0[i] = edge_verts[i*2] # first vertex indices for each edge
|
||||
id1[i] = edge_verts[i*2+1] # second vertex indices for each edge
|
||||
return id0, id1
|
||||
|
||||
#@guvectorize(['(float64[:] ,float64[:] , float64[:], float64[:], float64[:])'],'(m),(n),(n),(n),(n)',target='parallel')
|
||||
@njit(parallel=True)
|
||||
#@njit
|
||||
def numba_rd_laplacian_(edge_verts, a, b, lap_a, lap_b):
|
||||
for i in prange(len(edge_verts)/2):
|
||||
v0 = edge_verts[i*2]
|
||||
v1 = edge_verts[i*2+1]
|
||||
lap_a[v0] += a[v1] - a[v0]
|
||||
lap_a[v1] += a[v0] - a[v1]
|
||||
lap_b[v0] += b[v1] - b[v0]
|
||||
lap_b[v1] += b[v0] - b[v1]
|
||||
#return lap_a, lap_b
|
||||
|
||||
@njit(parallel=True)
|
||||
def rd_fill_laplacian(lap_a, lap_b, id0, id1, lap_a0, lap_b0):
|
||||
#for i, j, la0, lb0 in zip(id0,id1,lap_a0,lap_b0):
|
||||
for index in prange(len(id0)):
|
||||
i = id0[index]
|
||||
j = id1[index]
|
||||
la0 = lap_a0[index]
|
||||
lb0 = lap_b0[index]
|
||||
lap_a[i] += la0
|
||||
lap_b[i] += lb0
|
||||
lap_a[j] -= la0
|
||||
lap_b[j] -= lb0
|
||||
|
||||
@njit(parallel=True)
|
||||
def rd_init_laplacian(n_verts):
|
||||
lap_a = np.zeros(n_verts)
|
||||
lap_b = np.zeros(n_verts)
|
||||
return lap_a, lap_b
|
||||
|
||||
'''
|
||||
@jit
|
||||
def numba_reaction_diffusion(n_verts, n_edges, edge_verts, a, b, diff_a, diff_b, f, k, dt, time_steps):
|
||||
def numba_reaction_diffusion(n_verts, n_edges, edge_verts, a, b, diff_a, diff_b, f, k, dt, time_steps, db):
|
||||
arr = np.arange(n_edges)*2
|
||||
id0 = edge_verts[arr] # first vertex indices for each edge
|
||||
id1 = edge_verts[arr+1] # second vertex indices for each edge
|
||||
#dgrad = abs(grad[id1] - grad[id0])
|
||||
for i in range(time_steps):
|
||||
lap_a = np.zeros(n_verts)
|
||||
lap_b = np.zeros(n_verts)
|
||||
lap_a0 = a[id1] - a[id0] # laplacian increment for first vertex of each edge
|
||||
b += db
|
||||
lap_a0 = a[id1] - a[id0] # laplacian increment for first vertex of each edge
|
||||
lap_b0 = b[id1] - b[id0] # laplacian increment for first vertex of each edge
|
||||
#lap_a0 *= dgrad
|
||||
#lap_b0 *= dgrad
|
||||
|
||||
for i, j, la0, lb0 in zip(id0,id1,lap_a0,lap_b0):
|
||||
lap_a[i] += la0
|
||||
|
@ -26,5 +203,198 @@ try:
|
|||
a += (diff_a*lap_a - ab2 + f*(1-a))*dt
|
||||
b += (diff_b*lap_b + ab2 - (k+f)*b)*dt
|
||||
return a, b
|
||||
except:
|
||||
pass
|
||||
'''
|
||||
'''
|
||||
@njit(parallel=True)
|
||||
def numba_lerp2_(v00, v10, v01, v11, vx, vy):
|
||||
sh = v00.shape
|
||||
co2 = np.zeros((sh[0],len(vx),sh[-1]))
|
||||
for i in prange(len(v00)):
|
||||
for j in prange(len(vx)):
|
||||
for k in prange(len(v00[0][0])):
|
||||
co0 = v00[i][0][k] + (v10[i][0][k] - v00[i][0][k]) * vx[j][0]
|
||||
co1 = v01[i][0][k] + (v11[i][0][k] - v01[i][0][k]) * vx[j][0]
|
||||
co2[i][j][k] = co0 + (co1 - co0) * vy[j][0]
|
||||
return co2
|
||||
|
||||
|
||||
@njit(parallel=True)
|
||||
def numba_lerp2_vec(v0, vx, vy):
|
||||
n_faces = v0.shape[0]
|
||||
co2 = np.zeros((n_faces,len(vx),3))
|
||||
for i in prange(n_faces):
|
||||
for j in prange(len(vx)):
|
||||
for k in prange(3):
|
||||
co0 = v0[i][0][k] + (v0[i][1][k] - v0[i][0][k]) * vx[j][0]
|
||||
co1 = v0[i][3][k] + (v0[i][2][k] - v0[i][3][k]) * vx[j][0]
|
||||
co2[i][j][k] = co0 + (co1 - co0) * vy[j][0]
|
||||
return co2
|
||||
|
||||
@njit(parallel=True)
|
||||
def numba_lerp2__(val, vx, vy):
|
||||
n_faces = len(val)
|
||||
co2 = np.zeros((n_faces,len(vx),1))
|
||||
for i in prange(n_faces):
|
||||
for j in prange(len(vx)):
|
||||
co0 = val[i][0] + (val[i][1] - val[i][0]) * val[j][0]
|
||||
co1 = val[i][3] + (val[i][2] - val[i][3]) * val[j][0]
|
||||
co2[i][j][0] = co0 + (co1 - co0) * vy[j][0]
|
||||
return co2
|
||||
'''
|
||||
|
||||
@njit(parallel=True)
|
||||
def numba_combine_and_flatten(arrays):
|
||||
n_faces = len(arrays)
|
||||
n_verts = len(arrays[0])
|
||||
new_list = [0.0]*n_faces*n_verts*3
|
||||
for i in prange(n_faces):
|
||||
for j in prange(n_verts):
|
||||
for k in prange(3):
|
||||
new_list[i*n_verts*3+j*3+k] = arrays[i][j,k]
|
||||
return new_list
|
||||
|
||||
@njit(parallel=True)
|
||||
def numba_calc_thickness_area_weight(co2,n2,vz,a,weight):
|
||||
shape = co2.shape
|
||||
n_patches = shape[0]
|
||||
n_verts = shape[1]
|
||||
n_co = shape[2]
|
||||
nn = n2.shape[1]-1
|
||||
na = a.shape[1]-1
|
||||
nw = weight.shape[1]-1
|
||||
co3 = np.zeros((n_patches,n_verts,n_co))
|
||||
for i in prange(n_patches):
|
||||
for j in prange(n_verts):
|
||||
for k in prange(n_co):
|
||||
co3[i,j,k] = co2[i,j,k] + n2[i,min(j,nn),k] * vz[0,j,0] * a[i,min(j,na),0] * weight[i,min(j,nw),0]
|
||||
return co3
|
||||
'''
|
||||
@njit(parallel=True)
|
||||
def numba_calc_thickness_area(co2,n2,vz,a):
|
||||
shape = co2.shape
|
||||
n_patches = shape[0]
|
||||
n_verts = shape[1]
|
||||
n_co = shape[2]
|
||||
#co3 = [0.0]*n_patches*n_verts*n_co #np.zeros((n_patches,n_verts,n_co))
|
||||
co3 = np.zeros((n_patches,n_verts,n_co))
|
||||
for i in prange(n_patches):
|
||||
for j in prange(n_verts):
|
||||
for k in prange(n_co):
|
||||
#co3[i,j,k] = co2[i,j,k] + n2[i,j,k] * vz[0,j,0] * a[i,j,0]
|
||||
co3[i,j,k] = co2[i,j,k] + n2[i,min(j,nor_len),k] * vz[0,j,0] * a[i,j,0]
|
||||
return co3
|
||||
'''
|
||||
@njit(parallel=True)
|
||||
def numba_calc_thickness_weight(co2,n2,vz,weight):
|
||||
shape = co2.shape
|
||||
n_patches = shape[0]
|
||||
n_verts = shape[1]
|
||||
n_co = shape[2]
|
||||
nn = n2.shape[1]-1
|
||||
nw = weight.shape[1]-1
|
||||
co3 = np.zeros((n_patches,n_verts,n_co))
|
||||
for i in prange(n_patches):
|
||||
for j in prange(n_verts):
|
||||
for k in prange(n_co):
|
||||
co3[i,j,k] = co2[i,j,k] + n2[i,min(j,nn),k] * vz[0,j,0] * weight[i,min(j,nw),0]
|
||||
return co3
|
||||
|
||||
@njit(parallel=True)
|
||||
def numba_calc_thickness(co2,n2,vz):
|
||||
shape = co2.shape
|
||||
n_patches = shape[0]
|
||||
n_verts = shape[1]
|
||||
n_co = shape[2]
|
||||
nn = n2.shape[1]-1
|
||||
co3 = np.zeros((n_patches,n_verts,n_co))
|
||||
for i in prange(n_patches):
|
||||
for j in prange(n_verts):
|
||||
for k in prange(n_co):
|
||||
co3[i,j,k] = co2[i,j,k] + n2[i,min(j,nn),k] * vz[0,j,0]
|
||||
return co3
|
||||
|
||||
@njit(parallel=True)
|
||||
def numba_interp_points(v00, v10, v01, v11, vx, vy):
|
||||
n_patches = v00.shape[0]
|
||||
n_verts = vx.shape[1]
|
||||
n_verts0 = v00.shape[1]
|
||||
n_co = v00.shape[2]
|
||||
vxy = np.zeros((n_patches,n_verts,n_co))
|
||||
for i in prange(n_patches):
|
||||
for j in prange(n_verts):
|
||||
j0 = min(j,n_verts0-1)
|
||||
for k in prange(n_co):
|
||||
co0 = v00[i,j0,k] + (v10[i,j0,k] - v00[i,j0,k]) * vx[0,j,0]
|
||||
co1 = v01[i,j0,k] + (v11[i,j0,k] - v01[i,j0,k]) * vx[0,j,0]
|
||||
vxy[i,j,k] = co0 + (co1 - co0) * vy[0,j,0]
|
||||
return vxy
|
||||
|
||||
@njit(parallel=True)
|
||||
def numba_interp_points_sk(v00, v10, v01, v11, vx, vy):
|
||||
n_patches = v00.shape[0]
|
||||
n_sk = v00.shape[1]
|
||||
n_verts = v00.shape[2]
|
||||
n_co = v00.shape[3]
|
||||
vxy = np.zeros((n_patches,n_sk,n_verts,n_co))
|
||||
for i in prange(n_patches):
|
||||
for sk in prange(n_sk):
|
||||
for j in prange(n_verts):
|
||||
for k in prange(n_co):
|
||||
co0 = v00[i,sk,j,k] + (v10[i,sk,j,k] - v00[i,sk,j,k]) * vx[0,sk,j,0]
|
||||
co1 = v01[i,sk,j,k] + (v11[i,sk,j,k] - v01[i,sk,j,k]) * vx[0,sk,j,0]
|
||||
vxy[i,sk,j,k] = co0 + (co1 - co0) * vy[0,sk,j,0]
|
||||
return vxy
|
||||
|
||||
@njit
|
||||
def numba_lerp(v0, v1, x):
|
||||
return v0 + (v1 - v0) * x
|
||||
|
||||
@njit
|
||||
def numba_lerp2(v00, v10, v01, v11, vx, vy):
|
||||
co0 = numba_lerp(v00, v10, vx)
|
||||
co1 = numba_lerp(v01, v11, vx)
|
||||
co2 = numba_lerp(co0, co1, vy)
|
||||
return co2
|
||||
|
||||
@njit(parallel=True)
|
||||
def numba_lerp2_________________(v00, v10, v01, v11, vx, vy):
|
||||
ni = len(v00)
|
||||
nj = len(v00[0])
|
||||
nk = len(v00[0][0])
|
||||
co2 = np.zeros((ni,nj,nk))
|
||||
for i in prange(ni):
|
||||
for j in prange(nj):
|
||||
for k in prange(nk):
|
||||
_v00 = v00[i,j,k]
|
||||
_v01 = v01[i,j,k]
|
||||
_v10 = v10[i,j,k]
|
||||
_v11 = v11[i,j,k]
|
||||
co0 = _v00 + (_v10 - _v00) * vx[i,j,k]
|
||||
co1 = _v01 + (_v11 - _v01) * vx[i,j,k]
|
||||
co2[i,j,k] = co0 + (co1 - co0) * vy[i,j,k]
|
||||
return co2
|
||||
|
||||
@njit(parallel=True)
|
||||
def numba_lerp2_4(v00, v10, v01, v11, vx, vy):
|
||||
ni = len(v00)
|
||||
nj = len(v00[0])
|
||||
nk = len(v00[0][0])
|
||||
nw = len(v00[0][0][0])
|
||||
co2 = np.zeros((ni,nj,nk,nw))
|
||||
for i in prange(ni):
|
||||
for j in prange(nj):
|
||||
for k in prange(nk):
|
||||
for w in prange(nw):
|
||||
_v00 = v00[i,j,k]
|
||||
_v01 = v01[i,j,k]
|
||||
_v10 = v10[i,j,k]
|
||||
_v11 = v11[i,j,k]
|
||||
co0 = _v00 + (_v10 - _v00) * vx[i,j,k]
|
||||
co1 = _v01 + (_v11 - _v01) * vx[i,j,k]
|
||||
co2[i,j,k] = co0 + (co1 - co0) * vy[i,j,k]
|
||||
return co2
|
||||
|
||||
|
||||
#except:
|
||||
# print("Tissue: Numba cannot be installed. Try to restart Blender.")
|
||||
# pass
|
||||
|
|
|
@ -0,0 +1,557 @@
|
|||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# ---------------------------- ADAPTIVE DUPLIFACES --------------------------- #
|
||||
# ------------------------------- version 0.84 ------------------------------- #
|
||||
# #
|
||||
# Creates duplicates of selected mesh to active morphing the shape according #
|
||||
# to target faces. #
|
||||
# #
|
||||
# (c) Alessandro Zomparelli #
|
||||
# (2017) #
|
||||
# #
|
||||
# http://www.co-de-it.com/ #
|
||||
# #
|
||||
# ############################################################################ #
|
||||
|
||||
|
||||
import bpy
|
||||
from bpy.types import (
|
||||
Operator,
|
||||
Panel,
|
||||
PropertyGroup,
|
||||
)
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
EnumProperty,
|
||||
FloatProperty,
|
||||
IntProperty,
|
||||
StringProperty,
|
||||
PointerProperty
|
||||
)
|
||||
from mathutils import Vector, Quaternion, Matrix
|
||||
import numpy as np
|
||||
from math import *
|
||||
import random, time, copy
|
||||
import bmesh
|
||||
from .utils import *
|
||||
|
||||
class polyhedra_wireframe(Operator):
|
||||
bl_idname = "object.polyhedra_wireframe"
|
||||
bl_label = "Tissue Polyhedra Wireframe"
|
||||
bl_description = "Generate wireframes around the faces.\
|
||||
\nDoesn't works with boundary edges.\
|
||||
\n(Experimental)"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
thickness : FloatProperty(
|
||||
name="Thickness", default=0.1, min=0.001, soft_max=200,
|
||||
description="Wireframe thickness"
|
||||
)
|
||||
|
||||
subdivisions : IntProperty(
|
||||
name="Segments", default=1, min=1, soft_max=10,
|
||||
description="Max sumber of segments, used for the longest edge"
|
||||
)
|
||||
|
||||
#regular_sections : BoolProperty(
|
||||
# name="Regular Sections", default=False,
|
||||
# description="Turn inner loops into polygons"
|
||||
# )
|
||||
|
||||
dissolve_inners : BoolProperty(
|
||||
name="Dissolve Inners", default=False,
|
||||
description="Dissolve inner edges"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
try:
|
||||
#bool_tessellated = context.object.tissue_tessellate.generator != None
|
||||
ob = context.object
|
||||
return ob.type == 'MESH' and ob.mode == 'OBJECT'# and bool_tessellated
|
||||
except:
|
||||
return False
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
merge_dist = self.thickness*0.001
|
||||
|
||||
subs = self.subdivisions
|
||||
|
||||
start_time = time.time()
|
||||
ob = context.object
|
||||
me = simple_to_mesh(ob)
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(me)
|
||||
|
||||
bm.verts.ensure_lookup_table()
|
||||
bm.edges.ensure_lookup_table()
|
||||
bm.faces.ensure_lookup_table()
|
||||
|
||||
# Subdivide edges
|
||||
proportional_subs = True
|
||||
if subs > 1 and proportional_subs:
|
||||
wire_length = [e.calc_length() for e in bm.edges]
|
||||
all_edges = list(bm.edges)
|
||||
max_segment = max(wire_length)/subs
|
||||
split_edges = [[] for i in range(subs+1)]
|
||||
for e, l in zip(all_edges, wire_length):
|
||||
split_edges[int(l//max_segment)].append(e)
|
||||
for i in range(2,subs):
|
||||
perc = {}
|
||||
for e in split_edges[i]:
|
||||
perc[e]=0.1
|
||||
bmesh.ops.bisect_edges(bm, edges=split_edges[i], cuts=i, edge_percents=perc)
|
||||
|
||||
### Create double faces
|
||||
double_faces = []
|
||||
double_layer_edge = []
|
||||
double_layer_piece = []
|
||||
for f in bm.faces:
|
||||
verts0 = [v.co for v in f.verts]
|
||||
verts1 = [v.co for v in f.verts]
|
||||
verts1.reverse()
|
||||
double_faces.append(verts0)
|
||||
double_faces.append(verts1)
|
||||
|
||||
# Create new bmesh object and data layers
|
||||
bm1 = bmesh.new()
|
||||
|
||||
# Create faces and assign Edge Layers
|
||||
for verts in double_faces:
|
||||
new_verts = []
|
||||
for v in verts:
|
||||
vert = bm1.verts.new(v)
|
||||
new_verts.append(vert)
|
||||
bm1.faces.new(new_verts)
|
||||
|
||||
bm1.verts.ensure_lookup_table()
|
||||
bm1.edges.ensure_lookup_table()
|
||||
bm1.faces.ensure_lookup_table()
|
||||
|
||||
n_faces = len(bm.faces)
|
||||
n_doubles = len(bm1.faces)
|
||||
|
||||
polyhedra = []
|
||||
|
||||
for e in bm.edges:
|
||||
done = []
|
||||
|
||||
# ERROR: Naked edges
|
||||
e_faces = len(e.link_faces)
|
||||
if e_faces < 2:
|
||||
bm.free()
|
||||
bm1.free()
|
||||
message = "Naked edges are not allowed"
|
||||
self.report({'ERROR'}, message)
|
||||
return {'CANCELLED'}
|
||||
|
||||
edge_vec = e.verts[1].co - e.verts[0].co
|
||||
|
||||
# run first face
|
||||
for i1 in range(e_faces-1):
|
||||
f1 = e.link_faces[i1]
|
||||
#edge_verts1 = [v.index for v in f1.verts if v in e.verts]
|
||||
verts1 = [v.index for v in f1.verts]
|
||||
va1 = verts1.index(e.verts[0].index)
|
||||
vb1 = verts1.index(e.verts[1].index)
|
||||
# chech if order of the edge matches the order of the face
|
||||
dir1 = va1 == (vb1+1)%len(verts1)
|
||||
edge_vec1 = edge_vec if dir1 else -edge_vec
|
||||
|
||||
# run second face
|
||||
faces2 = []
|
||||
normals2 = []
|
||||
for i2 in range(i1+1,e_faces):
|
||||
#for i2 in range(n_faces):
|
||||
if i1 == i2: continue
|
||||
f2 = e.link_faces[i2]
|
||||
f2.normal_update()
|
||||
#edge_verts2 = [v.index for v in f2.verts if v in e.verts]
|
||||
verts2 = [v.index for v in f2.verts]
|
||||
va2 = verts2.index(e.verts[0].index)
|
||||
vb2 = verts2.index(e.verts[1].index)
|
||||
# chech if order of the edge matches the order of the face
|
||||
dir2 = va2 == (vb2+1)%len(verts2)
|
||||
# check for normal consistency
|
||||
if dir1 != dir2:
|
||||
# add face
|
||||
faces2.append(f2.index+1)
|
||||
normals2.append(f2.normal)
|
||||
else:
|
||||
# add flipped face
|
||||
faces2.append(-(f2.index+1))
|
||||
normals2.append(-f2.normal)
|
||||
|
||||
|
||||
|
||||
# find first polyhedra (positive)
|
||||
plane_x = f1.normal # normal
|
||||
plane_y = plane_x.cross(edge_vec1) # tangent face perp edge
|
||||
id1 = (f1.index+1)
|
||||
|
||||
min_angle0 = 10000
|
||||
|
||||
# check consistent faces
|
||||
if id1 not in done:
|
||||
id2 = None
|
||||
min_angle = min_angle0
|
||||
for i2, n2 in zip(faces2,normals2):
|
||||
v2 = flatten_vector(-n2, plane_x, plane_y)
|
||||
angle = vector_rotation(v2)
|
||||
if angle < min_angle:
|
||||
id2 = i2
|
||||
min_angle = angle
|
||||
if id2: done.append(id2)
|
||||
new_poly = True
|
||||
# add to existing polyhedron
|
||||
for p in polyhedra:
|
||||
if id1 in p or id2 in p:
|
||||
new_poly = False
|
||||
if id2 not in p: p.append(id2)
|
||||
if id1 not in p: p.append(id1)
|
||||
break
|
||||
# start new polyhedron
|
||||
if new_poly: polyhedra.append([id1, id2])
|
||||
|
||||
# find second polyhedra (negative)
|
||||
plane_x = -f1.normal # normal
|
||||
plane_y = plane_x.cross(-edge_vec1) # tangent face perp edge
|
||||
id1 = -(f1.index+1)
|
||||
|
||||
if id1 not in done:
|
||||
id2 = None
|
||||
min_angle = min_angle0
|
||||
for i2, n2 in zip(faces2, normals2):
|
||||
v2 = flatten_vector(n2, plane_x, plane_y)
|
||||
angle = vector_rotation(v2)
|
||||
if angle < min_angle:
|
||||
id2 = -i2
|
||||
min_angle = angle
|
||||
done.append(id2)
|
||||
add = True
|
||||
for p in polyhedra:
|
||||
if id1 in p or id2 in p:
|
||||
add = False
|
||||
if id2 not in p: p.append(id2)
|
||||
if id1 not in p: p.append(id1)
|
||||
break
|
||||
if add: polyhedra.append([id1, id2])
|
||||
|
||||
for i in range(len(bm1.faces)):
|
||||
for j in (False,True):
|
||||
if j: id = i+1
|
||||
else: id = -(i+1)
|
||||
join = []
|
||||
keep = []
|
||||
for p in polyhedra:
|
||||
if id in p: join += p
|
||||
else: keep.append(p)
|
||||
if len(join) > 0:
|
||||
keep.append(list(dict.fromkeys(join)))
|
||||
polyhedra = keep
|
||||
|
||||
for i, p in enumerate(polyhedra):
|
||||
for j in p:
|
||||
bm1.faces[j].material_index = i
|
||||
|
||||
end_time = time.time()
|
||||
print('Tissue: Polyhedra wireframe, found {} polyhedra in {:.4f} sec'.format(len(polyhedra), end_time-start_time))
|
||||
|
||||
|
||||
delete_faces = []
|
||||
wireframe_faces = []
|
||||
not_wireframe_faces = []
|
||||
flat_faces = []
|
||||
|
||||
bm.free()
|
||||
|
||||
#bmesh.ops.bisect_edges(bm1, edges=bm1.edges, cuts=3)
|
||||
|
||||
end_time = time.time()
|
||||
print('Tissue: Polyhedra wireframe, subdivide edges in {:.4f} sec'.format(end_time-start_time))
|
||||
|
||||
bm1.faces.index_update()
|
||||
#merge_verts = []
|
||||
for p in polyhedra:
|
||||
delete_faces_poly = []
|
||||
wireframe_faces_poly = []
|
||||
faces_id = [(f-1)*2 if f > 0 else (-f-1)*2+1 for f in p]
|
||||
faces_id_neg = [(-f-1)*2 if -f > 0 else (f-1)*2+1 for f in p]
|
||||
merge_verts = []
|
||||
faces = [bm1.faces[f_id] for f_id in faces_id]
|
||||
for f in faces:
|
||||
delete = False
|
||||
if f.index in delete_faces: continue
|
||||
'''
|
||||
cen = f.calc_center_median()
|
||||
for e in f.edges:
|
||||
mid = (e.verts[0].co + e.verts[1].co)/2
|
||||
vec1 = e.verts[0].co - e.verts[1].co
|
||||
vec2 = mid - cen
|
||||
ang = Vector.angle(vec1,vec2)
|
||||
length = vec2.length
|
||||
#length = sin(ang)*length
|
||||
if length < self.thickness/2:
|
||||
delete = True
|
||||
'''
|
||||
if False:
|
||||
sides = len(f.verts)
|
||||
for i in range(sides):
|
||||
v = f.verts[i].co
|
||||
v0 = f.verts[(i-1)%sides].co
|
||||
v1 = f.verts[(i+1)%sides].co
|
||||
vec0 = v0 - v
|
||||
vec1 = v1 - v
|
||||
ang = (pi - vec0.angle(vec1))/2
|
||||
length = min(vec0.length, vec1.length)*sin(ang)
|
||||
if length < self.thickness/2:
|
||||
delete = True
|
||||
break
|
||||
|
||||
if delete:
|
||||
delete_faces_poly.append(f.index)
|
||||
else:
|
||||
wireframe_faces_poly.append(f.index)
|
||||
merge_verts += [v for v in f.verts]
|
||||
if len(wireframe_faces_poly) < 2:
|
||||
delete_faces += faces_id
|
||||
not_wireframe_faces += faces_id_neg
|
||||
else:
|
||||
wireframe_faces += wireframe_faces_poly
|
||||
flat_faces += delete_faces_poly
|
||||
|
||||
#wireframe_faces = list(dict.fromkeys(wireframe_faces))
|
||||
bmesh.ops.remove_doubles(bm1, verts=merge_verts, dist=merge_dist)
|
||||
bm1.edges.ensure_lookup_table()
|
||||
bm1.faces.ensure_lookup_table()
|
||||
bm1.faces.index_update()
|
||||
|
||||
|
||||
wireframe_faces = [i for i in wireframe_faces if i not in not_wireframe_faces]
|
||||
wireframe_faces = list(dict.fromkeys(wireframe_faces))
|
||||
|
||||
flat_faces = list(dict.fromkeys(flat_faces))
|
||||
|
||||
end_time = time.time()
|
||||
print('Tissue: Polyhedra wireframe, merge and delete in {:.4f} sec'.format(end_time-start_time))
|
||||
|
||||
poly_me = me.copy()
|
||||
bm1.to_mesh(poly_me)
|
||||
poly_me.update()
|
||||
new_ob = bpy.data.objects.new("Polyhedra", poly_me)
|
||||
context.collection.objects.link(new_ob)
|
||||
|
||||
############# FRAME #############
|
||||
bm1.faces.index_update()
|
||||
wireframe_faces = [bm1.faces[i] for i in wireframe_faces]
|
||||
original_faces = wireframe_faces
|
||||
#bmesh.ops.remove_doubles(bm1, verts=merge_verts, dist=0.001)
|
||||
|
||||
# detect edge loops
|
||||
|
||||
loops = []
|
||||
boundaries_mat = []
|
||||
neigh_face_center = []
|
||||
face_normals = []
|
||||
|
||||
# compute boundary frames
|
||||
new_faces = []
|
||||
wire_length = []
|
||||
vert_ids = []
|
||||
|
||||
# append regular faces
|
||||
|
||||
for f in original_faces:
|
||||
loop = list(f.verts)
|
||||
loops.append(loop)
|
||||
boundaries_mat.append([f.material_index for v in loop])
|
||||
f.normal_update()
|
||||
face_normals.append([f.normal for v in loop])
|
||||
|
||||
push_verts = []
|
||||
inner_loops = []
|
||||
|
||||
for loop_index, loop in enumerate(loops):
|
||||
is_boundary = loop_index < len(neigh_face_center)
|
||||
materials = boundaries_mat[loop_index]
|
||||
new_loop = []
|
||||
loop_ext = [loop[-1]] + loop + [loop[0]]
|
||||
|
||||
# calc tangents
|
||||
tangents = []
|
||||
for i in range(len(loop)):
|
||||
# vertices
|
||||
vert0 = loop_ext[i]
|
||||
vert = loop_ext[i+1]
|
||||
vert1 = loop_ext[i+2]
|
||||
# edge vectors
|
||||
vec0 = (vert0.co - vert.co).normalized()
|
||||
vec1 = (vert.co - vert1.co).normalized()
|
||||
# tangent
|
||||
_vec1 = -vec1
|
||||
_vec0 = -vec0
|
||||
ang = (pi - vec0.angle(vec1))/2
|
||||
normal = face_normals[loop_index][i]
|
||||
tan0 = normal.cross(vec0)
|
||||
tan1 = normal.cross(vec1)
|
||||
tangent = (tan0 + tan1).normalized()/sin(ang)*self.thickness/2
|
||||
tangents.append(tangent)
|
||||
|
||||
# calc correct direction for boundaries
|
||||
mult = -1
|
||||
if is_boundary:
|
||||
dir_val = 0
|
||||
for i in range(len(loop)):
|
||||
surf_point = neigh_face_center[loop_index][i]
|
||||
tangent = tangents[i]
|
||||
vert = loop_ext[i+1]
|
||||
dir_val += tangent.dot(vert.co - surf_point)
|
||||
if dir_val > 0: mult = 1
|
||||
|
||||
# add vertices
|
||||
for i in range(len(loop)):
|
||||
vert = loop_ext[i+1]
|
||||
area = 1
|
||||
new_co = vert.co + tangents[i] * mult * area
|
||||
# add vertex
|
||||
new_vert = bm1.verts.new(new_co)
|
||||
new_loop.append(new_vert)
|
||||
vert_ids.append(vert.index)
|
||||
new_loop.append(new_loop[0])
|
||||
|
||||
# add faces
|
||||
#materials += [materials[0]]
|
||||
for i in range(len(loop)):
|
||||
v0 = loop_ext[i+1]
|
||||
v1 = loop_ext[i+2]
|
||||
v2 = new_loop[i+1]
|
||||
v3 = new_loop[i]
|
||||
face_verts = [v1,v0,v3,v2]
|
||||
if mult == -1: face_verts = [v0,v1,v2,v3]
|
||||
new_face = bm1.faces.new(face_verts)
|
||||
# Material by original edges
|
||||
piece_id = 0
|
||||
new_face.select = True
|
||||
new_faces.append(new_face)
|
||||
wire_length.append((v0.co - v1.co).length)
|
||||
max_segment = max(wire_length)/self.subdivisions
|
||||
#for f,l in zip(new_faces,wire_length):
|
||||
# f.material_index = min(int(l/max_segment), self.subdivisions-1)
|
||||
bm1.verts.ensure_lookup_table()
|
||||
push_verts += [v.index for v in loop_ext]
|
||||
|
||||
# At this point topology han been build, but not yet thickened
|
||||
|
||||
end_time = time.time()
|
||||
print('Tissue: Polyhedra wireframe, frames in {:.4f} sec'.format(end_time-start_time))
|
||||
|
||||
bm1.verts.ensure_lookup_table()
|
||||
bm1.edges.ensure_lookup_table()
|
||||
bm1.faces.ensure_lookup_table()
|
||||
bm1.verts.index_update()
|
||||
|
||||
### Displace vertices ###
|
||||
|
||||
circle_center = [0]*len(bm1.verts)
|
||||
circle_normal = [0]*len(bm1.verts)
|
||||
|
||||
smooth_corners = [True] * len(bm1.verts)
|
||||
corners = [[] for i in range(len(bm1.verts))]
|
||||
normals = [0]*len(bm1.verts)
|
||||
vertices = [0]*len(bm1.verts)
|
||||
# Define vectors direction
|
||||
for f in new_faces:
|
||||
v0 = f.verts[0]
|
||||
v1 = f.verts[1]
|
||||
id = v0.index
|
||||
corners[id].append((v1.co - v0.co).normalized())
|
||||
normals[id] = v0.normal.copy()
|
||||
vertices[id] = v0
|
||||
smooth_corners[id] = False
|
||||
# Displace vertices
|
||||
for i, vecs in enumerate(corners):
|
||||
if len(vecs) > 0:
|
||||
v = vertices[i]
|
||||
nor = normals[i]
|
||||
ang = 0
|
||||
for vec in vecs:
|
||||
ang += nor.angle(vec)
|
||||
ang /= len(vecs)
|
||||
div = sin(ang)
|
||||
if div == 0: div = 1
|
||||
v.co += nor*self.thickness/2/div
|
||||
|
||||
end_time = time.time()
|
||||
print('Tissue: Polyhedra wireframe, corners displace in {:.4f} sec'.format(end_time-start_time))
|
||||
|
||||
# Removing original flat faces
|
||||
|
||||
flat_faces = [bm1.faces[i] for i in flat_faces]
|
||||
for f in flat_faces:
|
||||
f.material_index = self.subdivisions+1
|
||||
for v in f.verts:
|
||||
if smooth_corners[v.index]:
|
||||
v.co += v.normal*self.thickness/2
|
||||
smooth_corners[v.index] = False
|
||||
delete_faces = delete_faces + [f.index for f in original_faces]
|
||||
delete_faces = list(dict.fromkeys(delete_faces))
|
||||
delete_faces = [bm1.faces[i] for i in delete_faces]
|
||||
bmesh.ops.delete(bm1, geom=delete_faces, context='FACES')
|
||||
|
||||
bmesh.ops.remove_doubles(bm1, verts=bm1.verts, dist=merge_dist)
|
||||
bm1.faces.ensure_lookup_table()
|
||||
bm1.edges.ensure_lookup_table()
|
||||
bm1.verts.ensure_lookup_table()
|
||||
|
||||
if self.dissolve_inners:
|
||||
bm1.edges.index_update()
|
||||
dissolve_edges = []
|
||||
for f in bm1.faces:
|
||||
e = f.edges[2]
|
||||
if e not in dissolve_edges:
|
||||
dissolve_edges.append(e)
|
||||
bmesh.ops.dissolve_edges(bm1, edges=dissolve_edges, use_verts=True, use_face_split=True)
|
||||
|
||||
all_lines = [[] for e in me.edges]
|
||||
all_end_points = [[] for e in me.edges]
|
||||
for v in bm1.verts: v.select_set(False)
|
||||
for f in bm1.faces: f.select_set(False)
|
||||
|
||||
_me = me.copy()
|
||||
bm1.to_mesh(me)
|
||||
me.update()
|
||||
new_ob = bpy.data.objects.new("Wireframe", me)
|
||||
context.collection.objects.link(new_ob)
|
||||
for o in context.scene.objects: o.select_set(False)
|
||||
new_ob.select_set(True)
|
||||
context.view_layer.objects.active = new_ob
|
||||
me = _me
|
||||
|
||||
bm1.free()
|
||||
bpy.data.meshes.remove(_me)
|
||||
#new_ob.location = ob.location
|
||||
new_ob.matrix_world = ob.matrix_world
|
||||
|
||||
end_time = time.time()
|
||||
print('Tissue: Polyhedra wireframe in {:.4f} sec'.format(end_time-start_time))
|
||||
return {'FINISHED'}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1510
mesh_tissue/utils.py
1510
mesh_tissue/utils.py
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,154 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Author: Stephen Leger (s-leger)
|
||||
#
|
||||
# ----------------------------------------------------------
|
||||
import bpy
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
PYPATH = sys.executable #bpy.app.binary_path_python
|
||||
|
||||
|
||||
class Pip:
|
||||
|
||||
def __init__(self):
|
||||
self._ensurepip()
|
||||
|
||||
@staticmethod
|
||||
def _ensure_user_site_package():
|
||||
import os
|
||||
import site
|
||||
import sys
|
||||
site_package = site.getusersitepackages()
|
||||
if not os.path.exists(site_package):
|
||||
site_package = bpy.utils.user_resource('SCRIPTS', "site_package", create=True)
|
||||
site.addsitedir(site_package)
|
||||
if site_package not in sys.path:
|
||||
sys.path.append(site_package)
|
||||
'''
|
||||
@staticmethod
|
||||
def _ensure_user_site_package():
|
||||
import os
|
||||
import site
|
||||
import sys
|
||||
site_package = site.getusersitepackages()
|
||||
if os.path.exists(site_package):
|
||||
if site_package not in sys.path:
|
||||
sys.path.append(site_package)
|
||||
else:
|
||||
site_package = bpy.utils.user_resource('SCRIPTS', "site_package", create=True)
|
||||
site.addsitedir(site_package)
|
||||
'''
|
||||
def _cmd(self, action, options, module):
|
||||
if options is not None and "--user" in options:
|
||||
self._ensure_user_site_package()
|
||||
|
||||
cmd = [PYPATH, "-m", "pip", action]
|
||||
|
||||
if options is not None:
|
||||
cmd.extend(options.split(" "))
|
||||
|
||||
cmd.append(module)
|
||||
return self._run(cmd)
|
||||
|
||||
def _popen(self, cmd):
|
||||
popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True)
|
||||
for stdout_line in iter(popen.stdout.readline, ""):
|
||||
yield stdout_line
|
||||
popen.stdout.close()
|
||||
popen.wait()
|
||||
|
||||
def _run(self, cmd):
|
||||
res = False
|
||||
status = ""
|
||||
for line in self._popen(cmd):
|
||||
if "ERROR:" in line:
|
||||
status = line.strip()
|
||||
if "Error:" in line:
|
||||
status = line.strip()
|
||||
print(line)
|
||||
if "Successfully" in line:
|
||||
status = line.strip()
|
||||
res = True
|
||||
return res, status
|
||||
|
||||
def _ensurepip(self):
|
||||
pip_not_found = False
|
||||
try:
|
||||
import pip
|
||||
except ImportError:
|
||||
pip_not_found = True
|
||||
pass
|
||||
if pip_not_found:
|
||||
self._run([PYPATH, "-m", "ensurepip", "--default-pip"])
|
||||
|
||||
@staticmethod
|
||||
def upgrade_pip():
|
||||
return Pip()._cmd("install", "--upgrade", "pip")
|
||||
|
||||
@staticmethod
|
||||
def uninstall(module, options=None):
|
||||
"""
|
||||
:param module: string module name with requirements see:[1]
|
||||
:param options: string command line options see:[2]
|
||||
:return: True on uninstall, False if already removed, raise on Error
|
||||
[1] https://pip.pypa.io/en/stable/reference/pip_install/#id29
|
||||
[2] https://pip.pypa.io/en/stable/reference/pip_install/#id47
|
||||
"""
|
||||
if options is None or options.strip() == "":
|
||||
# force confirm
|
||||
options = "-y"
|
||||
return Pip()._cmd("uninstall", options, module)
|
||||
|
||||
@staticmethod
|
||||
def install(module, options=None):
|
||||
"""
|
||||
:param module: string module name with requirements see:[1]
|
||||
:param options: string command line options see:[2]
|
||||
:return: True on install, False if already there, raise on Error
|
||||
[1] https://pip.pypa.io/en/stable/reference/pip_install/#id29
|
||||
[2] https://pip.pypa.io/en/stable/reference/pip_install/#id47
|
||||
"""
|
||||
if options is None or options.strip() == "":
|
||||
# store in user writable directory, use wheel, without deps
|
||||
options = "--user --only-binary all --no-deps"
|
||||
return Pip()._cmd("install", options, module)
|
||||
|
||||
@staticmethod
|
||||
def blender_version():
|
||||
"""
|
||||
:return: blender version tuple
|
||||
"""
|
||||
return bpy.app.version
|
||||
|
||||
@staticmethod
|
||||
def python_version():
|
||||
"""
|
||||
:return: python version object
|
||||
"""
|
||||
import sys
|
||||
# version.major, version.minor, version.micro
|
||||
return sys.version_info
|
|
@ -12,7 +12,7 @@
|
|||
# #
|
||||
# ############################################################################ #
|
||||
|
||||
import bpy
|
||||
import bpy, bmesh
|
||||
import math
|
||||
from bpy.types import Operator
|
||||
from bpy.props import BoolProperty
|
||||
|
@ -48,48 +48,45 @@ class uv_to_mesh(Operator):
|
|||
)
|
||||
|
||||
def execute(self, context):
|
||||
if context.mode == 'EDIT_MESH': on_selection = True
|
||||
else: on_selection = False
|
||||
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
for o in bpy.data.objects and bpy.context.view_layer.objects:
|
||||
o.select_set(False)
|
||||
bpy.context.object.select_set(True)
|
||||
ob0 = context.object
|
||||
for o in bpy.context.view_layer.objects: o.select_set(False)
|
||||
ob0.select_set(True)
|
||||
|
||||
if self.apply_modifiers:
|
||||
bpy.ops.object.duplicate_move()
|
||||
bpy.ops.object.convert(target='MESH')
|
||||
ob0 = bpy.context.object
|
||||
|
||||
# me0 = ob0.to_mesh(bpy.context.depsgraph, apply_modifiers=self.apply_modifiers)
|
||||
#if self.apply_modifiers: me0 = simple_to_mesh(ob0)
|
||||
#else: me0 = ob0.data.copy()
|
||||
name0 = ob0.name
|
||||
ob0 = convert_object_to_mesh(ob0, apply_modifiers=self.apply_modifiers, preserve_status=False)
|
||||
me0 = ob0.data
|
||||
area = 0
|
||||
|
||||
verts = []
|
||||
faces = []
|
||||
face_materials = []
|
||||
for face in me0.polygons:
|
||||
if on_selection: polygons = [f for f in me0.polygons if f.select]
|
||||
else: polygons = me0.polygons
|
||||
bm = bmesh.new()
|
||||
|
||||
for face in polygons:
|
||||
area += face.area
|
||||
uv_face = []
|
||||
store = False
|
||||
try:
|
||||
if len(me0.uv_layers) > 0:
|
||||
verts = []
|
||||
for loop in face.loop_indices:
|
||||
uv = me0.uv_layers.active.data[loop].uv
|
||||
if uv.x != 0 and uv.y != 0:
|
||||
store = True
|
||||
new_vert = Vector((uv.x, uv.y, 0))
|
||||
new_vert = bm.verts.new((uv.x, uv.y, 0))
|
||||
verts.append(new_vert)
|
||||
uv_face.append(loop)
|
||||
if store:
|
||||
faces.append(uv_face)
|
||||
face_materials.append(face.material_index)
|
||||
except:
|
||||
new_face = bm.faces.new(verts)
|
||||
new_face.material_index = face.material_index
|
||||
else:
|
||||
self.report({'ERROR'}, "Missing UV Map")
|
||||
|
||||
return {'CANCELLED'}
|
||||
|
||||
name = name0 + 'UV'
|
||||
name = name0 + '_UV'
|
||||
# Create mesh and object
|
||||
me = bpy.data.meshes.new(name + 'Mesh')
|
||||
ob = bpy.data.objects.new(name, me)
|
||||
|
@ -101,9 +98,10 @@ class uv_to_mesh(Operator):
|
|||
ob.select_set(True)
|
||||
|
||||
# Create mesh from given verts, faces.
|
||||
me.from_pydata(verts, [], faces)
|
||||
bm.to_mesh(me)
|
||||
# Update mesh with new data
|
||||
me.update()
|
||||
|
||||
if self.auto_scale:
|
||||
new_area = 0
|
||||
for p in me.polygons:
|
||||
|
@ -111,7 +109,6 @@ class uv_to_mesh(Operator):
|
|||
if new_area == 0:
|
||||
self.report({'ERROR'}, "Impossible to generate mesh from UV")
|
||||
bpy.data.objects.remove(ob0)
|
||||
|
||||
return {'CANCELLED'}
|
||||
|
||||
# VERTEX GROUPS
|
||||
|
@ -119,7 +116,7 @@ class uv_to_mesh(Operator):
|
|||
for group in ob0.vertex_groups:
|
||||
index = group.index
|
||||
ob.vertex_groups.new(name=group.name)
|
||||
for p in me0.polygons:
|
||||
for p in polygons:
|
||||
for vert, loop in zip(p.vertices, p.loop_indices):
|
||||
try:
|
||||
ob.vertex_groups[index].add([loop], group.weight(vert), 'REPLACE')
|
||||
|
@ -138,25 +135,12 @@ class uv_to_mesh(Operator):
|
|||
|
||||
# MATERIALS
|
||||
if self.materials:
|
||||
try:
|
||||
if len(ob0.material_slots) > 0:
|
||||
# assign old material
|
||||
uv_materials = [slot.material for slot in ob0.material_slots]
|
||||
for i in range(len(uv_materials)):
|
||||
bpy.ops.object.material_slot_add()
|
||||
bpy.context.object.material_slots[i].material = uv_materials[i]
|
||||
for i in range(len(ob.data.polygons)):
|
||||
ob.data.polygons[i].material_index = face_materials[i]
|
||||
except:
|
||||
pass
|
||||
'''
|
||||
if self.apply_modifiers:
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
ob.select_set(False)
|
||||
ob0.select_set(True)
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
ob.select_set(True)
|
||||
bpy.context.view_layer.objects.active = ob
|
||||
'''
|
||||
|
||||
bpy.data.objects.remove(ob0)
|
||||
bpy.data.meshes.remove(me0)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue