You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
156 lines
6.7 KiB
156 lines
6.7 KiB
import bpy
|
|
import bmesh
|
|
import os
|
|
import sys
|
|
import math
|
|
import numpy as np
|
|
import shutil
|
|
from mathutils import Vector
|
|
from ..project_config import PROJECT_CONFIG
|
|
import datetime
|
|
import json
|
|
|
|
|
|
def boundary_condition_mesh_callback(scene, context):
|
|
items = [("","empty","")]
|
|
object_list = bpy.context.scene.objects
|
|
for i in object_list:
|
|
if i.type == 'MESH':
|
|
items.append((i.name, i.name, i.name))
|
|
return items
|
|
|
|
def custom_normalize_array(arr, min_value=0, max_value=1, threshold=0.0007):
|
|
small_values = [x for x in arr if x <= threshold]
|
|
|
|
if small_values:
|
|
min_val_small = min(small_values)
|
|
max_val_small = max(small_values)
|
|
normalized_small = [
|
|
min_value + (x - min_val_small) / (max_val_small - min_val_small) * (max_value - min_value) for x in small_values
|
|
]
|
|
else:
|
|
normalized_small = []
|
|
|
|
large_values = [x for x in arr if x > threshold]
|
|
if large_values:
|
|
min_val_large = min(large_values)
|
|
max_val_large = max(large_values)
|
|
normalized_large = [
|
|
min_value + ((math.log(1 + x - threshold) / math.log(1 + max_val_large - threshold)) * (max_value - min_value)) for x in large_values
|
|
]
|
|
else:
|
|
normalized_large = []
|
|
|
|
normalized_arr = normalized_small + normalized_large
|
|
return normalized_arr
|
|
|
|
def get_neighbors(mesh, num_vertices):
|
|
neighbors = [set() for _ in range(num_vertices)]
|
|
for polygon in mesh.polygons:
|
|
for i in polygon.vertices:
|
|
if i < num_vertices:
|
|
for j in polygon.vertices:
|
|
if i != j and j < num_vertices:
|
|
neighbors[i].add(j)
|
|
return neighbors
|
|
|
|
|
|
# 进行颜色的平均
|
|
|
|
def average_colors(normalized_stress, neighbors, num_vertices, iterations=1, weight=0.1):
|
|
colors = np.array(normalized_stress[:num_vertices])
|
|
for _ in range(iterations):
|
|
for i, color in enumerate(colors):
|
|
neighbor_colors = np.array([colors[j] for j in neighbors[i]])
|
|
average_color = np.mean(neighbor_colors)
|
|
colors[i] = (1 - weight) * color + weight * average_color
|
|
return colors
|
|
|
|
class FEMTetrahedralOperator(bpy.types.Operator):
|
|
bl_idname: str = "designauto.quasistatic_simulation_fem_tetrahedral"
|
|
bl_label: str = "FEM仿真"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
# YM: bpy.props.FloatProperty(name="Young's modulus", default=1e5)
|
|
# PR: bpy.props.FloatProperty(name="Poisson's ratio", default=0.3)
|
|
# density: bpy.props.FloatProperty(name="density", default=1e3)
|
|
|
|
hole_height: bpy.props.FloatProperty(name="孔洞深度", default=0.1)
|
|
max_hole_radius: bpy.props.FloatProperty(name="最大半径", default=0.4)
|
|
min_hole_radius: bpy.props.FloatProperty(name="最小半径", default=0.2)
|
|
|
|
# nbc1: bpy.props.EnumProperty(name="NBC 1", items=boundary_condition_mesh_callback)
|
|
# nbcv1: bpy.props.FloatProperty(name="NBC va1 1", default=0)
|
|
|
|
# nbc2: bpy.props.EnumProperty(name="NBC 2", items=boundary_condition_mesh_callback)
|
|
# nbcv2: bpy.props.FloatProperty(name="NBC val 2", default=0)
|
|
|
|
# nbc3: bpy.props.EnumProperty(name="NBC 3", items=boundary_condition_mesh_callback)
|
|
# nbcv3: bpy.props.FloatProperty(name="NBC val 3", default=0)
|
|
|
|
# dbc1: bpy.props.EnumProperty(name="DBC 1", items=boundary_condition_mesh_callback)
|
|
# dbc2: bpy.props.EnumProperty(name="DBC 2", items=boundary_condition_mesh_callback)
|
|
# dbc3: bpy.props.EnumProperty(name="DBC 3", items=boundary_condition_mesh_callback)
|
|
|
|
|
|
def execute(self, context):
|
|
working_object = context.active_object
|
|
bpyscene = bpy.context.scene
|
|
working_name = 'fem'
|
|
workplace = PROJECT_CONFIG.workplace_dir_path
|
|
executable_path = PROJECT_CONFIG.executable_dir_path
|
|
fem_mesh_path = os.path.join(workplace, "parameters", "femmodel.obj")
|
|
import dapy_qss_fem_tetrahedral
|
|
bpy.ops.export_scene.obj(filepath=fem_mesh_path, use_selection=True, axis_forward='Y', axis_up='Z', use_edges=False, use_animation=False, use_materials=False, use_uvs=False, use_normals=False,
|
|
use_mesh_modifiers=False, use_nurbs=False, use_smooth_groups=False, use_vertex_groups=False, use_blen_objects=False, use_smooth_groups_bitflags=False)
|
|
os.chdir(executable_path)
|
|
surface_mesh, mat_deformed_coordinates, \
|
|
vtx_displacement, vtx_stress = \
|
|
dapy_qss_fem_tetrahedral.QuasistaticSimulationByFEMTetrahedral(self.hole_height,self.max_hole_radius,self.min_hole_radius)
|
|
|
|
|
|
|
|
|
|
working_object.hide_set(True)
|
|
new_mesh = bpy.data.meshes.new("mesh-" + working_object.name + "-hole")
|
|
new_mesh.from_pydata(surface_mesh.mat_coordinates.tolist(), [], surface_mesh.mat_faces.tolist())
|
|
new_mesh.update()
|
|
new_object = bpy.data.objects.new(working_object.name + "-hole", new_mesh)
|
|
#new_stress_mesh = bpy.data.meshes.new("mesh-" + working_name + "-stress")
|
|
new_stress_mesh = working_object.data
|
|
new_stress_mesh.update()
|
|
new_stress_object = bpy.data.objects.new(working_name + "-stress", new_stress_mesh)
|
|
|
|
normalized_stress = custom_normalize_array(vtx_stress, min_value=0.2, max_value=1, threshold=0.0007)
|
|
if not new_stress_mesh.vertex_colors:
|
|
new_stress_mesh.vertex_colors.new()
|
|
num_vertices = len(new_stress_mesh.vertices)
|
|
neighbors = get_neighbors(new_stress_mesh, num_vertices)
|
|
normalized_stress = average_colors(normalized_stress, neighbors, num_vertices, iterations=10, weight=0.2)
|
|
for polygon in new_stress_mesh.polygons:
|
|
for i, index in enumerate(polygon.vertices):
|
|
loop_index = polygon.loop_indices[i]
|
|
new_stress_mesh.vertex_colors.active.data[loop_index].color = (normalized_stress[index], 0, 0, 1)
|
|
found = False
|
|
for c in bpy.data.collections:
|
|
if c.name == 'designauto':
|
|
found = True
|
|
new_collection = c
|
|
if not found:
|
|
new_collection = bpy.data.collections.new('designauto')
|
|
bpy.context.scene.collection.children.link(new_collection)
|
|
|
|
new_collection.objects.link(new_object)
|
|
new_collection.objects.link(new_stress_object)
|
|
working_object.select_set(True)
|
|
return {'FINISHED'}
|
|
|
|
|
|
def invoke(self, context, event):
|
|
return context.window_manager.invoke_props_dialog(self)
|
|
# if context.active_object.type == 'MESH':
|
|
|
|
# return context.window_manager.invoke_props_dialog(self)
|
|
# else:
|
|
# self.report({'ERROR'}, "Selected object is not a mesh!")
|
|
# return {'CANCELLED'}
|
|
|