[Blender] Доработаны процедуры ретопологии мешей
This commit is contained in:
parent
45e0d29ea0
commit
6560a4359d
9 changed files with 188 additions and 204 deletions
|
@ -15,6 +15,7 @@ __version__ = "0.2"
|
||||||
import time
|
import time
|
||||||
import FreeCAD
|
import FreeCAD
|
||||||
import logging
|
import logging
|
||||||
|
import math
|
||||||
import xml
|
import xml
|
||||||
import sys
|
import sys
|
||||||
import xml.sax
|
import xml.sax
|
||||||
|
@ -24,7 +25,7 @@ import bpy
|
||||||
from bpy_extras.node_shader_utils import PrincipledBSDFWrapper
|
from bpy_extras.node_shader_utils import PrincipledBSDFWrapper
|
||||||
from import_fcstd.handler import FreeCAD_xml_handler
|
from import_fcstd.handler import FreeCAD_xml_handler
|
||||||
from import_fcstd.import_hierarchy import import_hierarchy
|
from import_fcstd.import_hierarchy import import_hierarchy
|
||||||
from import_fcstd.materials import set_fem_mat
|
from import_fcstd.import_materials import import_materials
|
||||||
from import_fcstd.is_object_solid import is_object_solid
|
from import_fcstd.is_object_solid import is_object_solid
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -32,7 +33,8 @@ logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
def obj_importer(filename,
|
def obj_importer(filename,
|
||||||
tessellation,
|
linear_deflection,
|
||||||
|
angular_deflection,
|
||||||
update=False,
|
update=False,
|
||||||
placement=True,
|
placement=True,
|
||||||
skiphidden=True,
|
skiphidden=True,
|
||||||
|
@ -42,8 +44,6 @@ def obj_importer(filename,
|
||||||
|
|
||||||
"""Reads a FreeCAD .FCStd file and creates Blender objects"""
|
"""Reads a FreeCAD .FCStd file and creates Blender objects"""
|
||||||
|
|
||||||
TRIANGULATE = True # set to True to triangulate all faces (will loose multimaterial info)
|
|
||||||
|
|
||||||
guidata = {}
|
guidata = {}
|
||||||
zdoc = zipfile.ZipFile(filename)
|
zdoc = zipfile.ZipFile(filename)
|
||||||
if zdoc:
|
if zdoc:
|
||||||
|
@ -78,7 +78,7 @@ def obj_importer(filename,
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
# import some FreeCAD modules needed below. After "import FreeCAD" these modules become available
|
# import some FreeCAD modules needed below. After "import FreeCAD" these modules become available
|
||||||
import Part
|
import Part, Mesh, MeshPart
|
||||||
|
|
||||||
def hascurves(shape):
|
def hascurves(shape):
|
||||||
|
|
||||||
|
@ -87,8 +87,14 @@ def obj_importer(filename,
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
fcstd_collection = bpy.data.collections.new("FreeCAD import")
|
parts_collection = bpy.data.collections.new("Import Parts")
|
||||||
bpy.context.scene.collection.children.link(fcstd_collection)
|
bpy.context.scene.collection.children.link(parts_collection)
|
||||||
|
|
||||||
|
# collect all materials
|
||||||
|
fem_mats = []
|
||||||
|
for fem_mat in doc.Objects:
|
||||||
|
if fem_mat.isDerivedFrom("App::MaterialObjectPython"):
|
||||||
|
fem_mats.append(fem_mat)
|
||||||
|
|
||||||
for obj in doc.Objects:
|
for obj in doc.Objects:
|
||||||
# logger.debug("Importing",obj.Label)
|
# logger.debug("Importing",obj.Label)
|
||||||
|
@ -100,6 +106,12 @@ def obj_importer(filename,
|
||||||
# TODO add parent visibility check
|
# TODO add parent visibility check
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# process simple parts only
|
||||||
|
if not obj.isDerivedFrom("Part::Feature"):
|
||||||
|
logger.debug('%s is not simple part', obj.Label)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# process solids only
|
||||||
if not is_object_solid(obj):
|
if not is_object_solid(obj):
|
||||||
logger.debug('%s is not solid', obj.Label)
|
logger.debug('%s is not solid', obj.Label)
|
||||||
continue
|
continue
|
||||||
|
@ -107,107 +119,25 @@ def obj_importer(filename,
|
||||||
verts = []
|
verts = []
|
||||||
edges = []
|
edges = []
|
||||||
faces = []
|
faces = []
|
||||||
matindex = [] # face to material relationship
|
|
||||||
faceedges = [] # a placeholder to store edges that belong to a face
|
|
||||||
|
|
||||||
if obj.isDerivedFrom("Part::Feature"):
|
# create mesh from shape
|
||||||
# !!!
|
shape = obj.Shape
|
||||||
# create mesh from shape
|
if placement:
|
||||||
shape = obj.Shape
|
placement = obj.Placement
|
||||||
if placement:
|
shape = obj.Shape.copy()
|
||||||
# !!!
|
shape.Placement = placement.inverse().multiply(shape.Placement)
|
||||||
placement = obj.Placement
|
meshfromshape = doc.addObject("Mesh::Feature","Mesh")
|
||||||
shape = obj.Shape.copy()
|
meshfromshape.Mesh = MeshPart.meshFromShape(
|
||||||
shape.Placement = placement.inverse().multiply(shape.Placement)
|
Shape=shape,
|
||||||
if shape.Faces:
|
LinearDeflection=linear_deflection,
|
||||||
# !!!
|
AngularDeflection=math.radians(angular_deflection),
|
||||||
if TRIANGULATE:
|
Relative=False)
|
||||||
# triangulate and make faces
|
|
||||||
rawdata = shape.tessellate(tessellation)
|
|
||||||
for v in rawdata[0]:
|
|
||||||
verts.append([v.x,v.y,v.z])
|
|
||||||
for f in rawdata[1]:
|
|
||||||
faces.append(f)
|
|
||||||
for face in shape.Faces:
|
|
||||||
for e in face.Edges:
|
|
||||||
faceedges.append(e.hashCode())
|
|
||||||
else:
|
|
||||||
# !!!
|
|
||||||
# write FreeCAD faces as polygons when possible
|
|
||||||
time_start = time.time()
|
|
||||||
for face in shape.Faces:
|
|
||||||
if (len(face.Wires) > 1) or (not isinstance(face.Surface,Part.Plane)) or hascurves(face):
|
|
||||||
# !!!
|
|
||||||
# face has holes or is curved, so we need to triangulate it
|
|
||||||
rawdata = face.tessellate(tessellation)
|
|
||||||
for v in rawdata[0]:
|
|
||||||
vl = [v.x,v.y,v.z]
|
|
||||||
if not vl in verts:
|
|
||||||
verts.append(vl)
|
|
||||||
for f in rawdata[1]:
|
|
||||||
nf = []
|
|
||||||
for vi in f:
|
|
||||||
nv = rawdata[0][vi]
|
|
||||||
nf.append(verts.index([nv.x,nv.y,nv.z]))
|
|
||||||
faces.append(nf)
|
|
||||||
matindex.append(len(rawdata[1]))
|
|
||||||
else:
|
|
||||||
# !!!
|
|
||||||
f = []
|
|
||||||
ov = face.OuterWire.OrderedVertexes
|
|
||||||
for v in ov:
|
|
||||||
vl = [v.X,v.Y,v.Z]
|
|
||||||
if not vl in verts:
|
|
||||||
verts.append(vl)
|
|
||||||
f.append(verts.index(vl))
|
|
||||||
# FreeCAD doesn't care about verts order. Make sure our loop goes clockwise
|
|
||||||
c = face.CenterOfMass
|
|
||||||
v1 = ov[0].Point.sub(c)
|
|
||||||
v2 = ov[1].Point.sub(c)
|
|
||||||
n = face.normalAt(0,0)
|
|
||||||
if (v1.cross(v2)).getAngle(n) > 1.57:
|
|
||||||
f.reverse() # inverting verts order if the direction is couterclockwise
|
|
||||||
faces.append(f)
|
|
||||||
matindex.append(1)
|
|
||||||
for e in face.Edges:
|
|
||||||
faceedges.append(e.hashCode())
|
|
||||||
logger.debug('faces time is %s', (time.time() - time_start))
|
|
||||||
for edge in shape.Edges:
|
|
||||||
# !!!
|
|
||||||
# Treat remaining edges (that are not in faces)
|
|
||||||
if not (edge.hashCode() in faceedges):
|
|
||||||
if hascurves(edge):
|
|
||||||
dv = edge.discretize(9) # TODO use tessellation value
|
|
||||||
for i in range(len(dv)-1):
|
|
||||||
dv1 = [dv[i].x,dv[i].y,dv[i].z]
|
|
||||||
dv2 = [dv[i+1].x,dv[i+1].y,dv[i+1].z]
|
|
||||||
if not dv1 in verts:
|
|
||||||
verts.append(dv1)
|
|
||||||
if not dv2 in verts:
|
|
||||||
verts.append(dv2)
|
|
||||||
edges.append([verts.index(dv1),verts.index(dv2)])
|
|
||||||
else:
|
|
||||||
e = []
|
|
||||||
for vert in edge.Vertexes:
|
|
||||||
# TODO discretize non-linear edges
|
|
||||||
v = [vert.X,vert.Y,vert.Z]
|
|
||||||
if not v in verts:
|
|
||||||
verts.append(v)
|
|
||||||
e.append(verts.index(v))
|
|
||||||
edges.append(e)
|
|
||||||
|
|
||||||
elif obj.isDerivedFrom("Mesh::Feature"):
|
t = meshfromshape.Mesh.Topology
|
||||||
# convert freecad mesh to blender mesh
|
verts = [[v.x,v.y,v.z] for v in t[0]]
|
||||||
mesh = obj.Mesh
|
faces = t[1]
|
||||||
if placement:
|
|
||||||
placement = obj.Placement
|
|
||||||
mesh = obj.Mesh.copy() # in meshes, this zeroes the placement
|
|
||||||
t = mesh.Topology
|
|
||||||
verts = [[v.x,v.y,v.z] for v in t[0]]
|
|
||||||
faces = t[1]
|
|
||||||
|
|
||||||
if verts and (faces or edges):
|
if verts and faces:
|
||||||
# !!!
|
|
||||||
# create or update object with mesh and material data
|
# create or update object with mesh and material data
|
||||||
bobj = None
|
bobj = None
|
||||||
bmat = None
|
bmat = None
|
||||||
|
@ -225,7 +155,6 @@ def obj_importer(filename,
|
||||||
# update only the mesh of existing object. Don't touch materials
|
# update only the mesh of existing object. Don't touch materials
|
||||||
bobj.data = bmesh
|
bobj.data = bmesh
|
||||||
else:
|
else:
|
||||||
# !!!
|
|
||||||
# create new object
|
# create new object
|
||||||
bobj = bpy.data.objects.new(obj.Label, bmesh)
|
bobj = bpy.data.objects.new(obj.Label, bmesh)
|
||||||
if placement:
|
if placement:
|
||||||
|
@ -239,17 +168,19 @@ def obj_importer(filename,
|
||||||
bobj.rotation_mode = m
|
bobj.rotation_mode = m
|
||||||
bobj.scale = (scale, scale, scale)
|
bobj.scale = (scale, scale, scale)
|
||||||
|
|
||||||
if obj.Name in guidata:
|
# one material for the whole object
|
||||||
# !!!
|
for fem_mat in fem_mats:
|
||||||
# one material for the whole object
|
for ref in fem_mat.References:
|
||||||
for fem_mat in doc.Objects:
|
if ref[0].Label == bobj.name:
|
||||||
set_fem_mat(obj, bobj, fem_mat)
|
import_materials(bobj, fem_mat)
|
||||||
|
|
||||||
|
parts_collection.objects.link(bobj)
|
||||||
|
|
||||||
|
# construct assembly hierarchy
|
||||||
obj_parent = obj.getParentGeoFeatureGroup()
|
obj_parent = obj.getParentGeoFeatureGroup()
|
||||||
if obj_parent:
|
if obj_parent:
|
||||||
import_hierarchy(obj, bobj, scale)
|
import_hierarchy(obj, bobj, scale)
|
||||||
|
|
||||||
fcstd_collection.objects.link(bobj)
|
|
||||||
if select:
|
if select:
|
||||||
bpy.context.view_layer.objects.active = bobj
|
bpy.context.view_layer.objects.active = bobj
|
||||||
bobj.select_set(True)
|
bobj.select_set(True)
|
||||||
|
|
|
@ -34,6 +34,14 @@ def empty_importer(path_json):
|
||||||
fori = tuple(pivot_pose['orientation'].values())
|
fori = tuple(pivot_pose['orientation'].values())
|
||||||
bori = (fori[3],)+fori[:3]
|
bori = (fori[3],)+fori[:3]
|
||||||
|
|
||||||
|
if not bpy.data.collections.get('Import LCS'):
|
||||||
|
lcs_collection = bpy.data.collections.new("Import LCS")
|
||||||
|
bpy.context.scene.collection.children.link(lcs_collection)
|
||||||
|
bpy.context.view_layer.active_layer_collection = \
|
||||||
|
bpy.context.view_layer.layer_collection.children['Import LCS']
|
||||||
|
else:
|
||||||
|
lcs_collection = bpy.data.collections['Import LCS']
|
||||||
|
|
||||||
bpy.ops.object.empty_add(
|
bpy.ops.object.empty_add(
|
||||||
type='ARROWS', radius=0.1, align='WORLD',
|
type='ARROWS', radius=0.1, align='WORLD',
|
||||||
location=(0, 0, 0), rotation=(0, 0, 0))
|
location=(0, 0, 0), rotation=(0, 0, 0))
|
||||||
|
@ -43,6 +51,7 @@ def empty_importer(path_json):
|
||||||
pivot_obj.location = loc
|
pivot_obj.location = loc
|
||||||
pivot_obj.rotation_quaternion = bori
|
pivot_obj.rotation_quaternion = bori
|
||||||
pivot_obj.rotation_mode = 'XYZ'
|
pivot_obj.rotation_mode = 'XYZ'
|
||||||
|
pivot_obj.show_in_front = True
|
||||||
|
|
||||||
if pivot_parent_name:
|
if pivot_parent_name:
|
||||||
pivot_obj.parent = bpy.data.objects[pivot_parent_name]
|
pivot_obj.parent = bpy.data.objects[pivot_parent_name]
|
||||||
|
|
|
@ -21,10 +21,19 @@ logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
def import_hierarchy(fc_obj, b_obj, scale):
|
def import_hierarchy(fc_obj, b_obj, scale):
|
||||||
"""FreeCAD object, Blender object, scene scale"""
|
"""FreeCAD object, Blender object, scene scale"""
|
||||||
|
|
||||||
|
if not bpy.data.collections.get('Import Hierarchy'):
|
||||||
|
hierarchy_collection = bpy.data.collections.new("Import Hierarchy")
|
||||||
|
bpy.context.scene.collection.children.link(hierarchy_collection)
|
||||||
|
bpy.context.view_layer.active_layer_collection = \
|
||||||
|
bpy.context.view_layer.layer_collection.children['Import Hierarchy']
|
||||||
|
else:
|
||||||
|
hierarchy_collection = bpy.data.collections['Import Hierarchy']
|
||||||
|
|
||||||
obj_parent = fc_obj.getParentGeoFeatureGroup()
|
obj_parent = fc_obj.getParentGeoFeatureGroup()
|
||||||
obj_child_name = None
|
obj_child_name = None
|
||||||
while obj_parent:
|
while obj_parent:
|
||||||
if bpy.context.scene.objects.get(obj_parent.Label):
|
if hierarchy_collection.objects.get(obj_parent.Label):
|
||||||
empty = bpy.data.objects[obj_parent.Label]
|
empty = bpy.data.objects[obj_parent.Label]
|
||||||
else:
|
else:
|
||||||
bpy.ops.object.empty_add(
|
bpy.ops.object.empty_add(
|
||||||
|
@ -40,11 +49,12 @@ def import_hierarchy(fc_obj, b_obj, scale):
|
||||||
q = (placement.Rotation.Q[3],)+placement.Rotation.Q[:3]
|
q = (placement.Rotation.Q[3],)+placement.Rotation.Q[:3]
|
||||||
empty.rotation_quaternion = (q)
|
empty.rotation_quaternion = (q)
|
||||||
empty.rotation_mode = rm
|
empty.rotation_mode = rm
|
||||||
if b_obj.parent:
|
if not b_obj.parent:
|
||||||
bpy.data.objects[obj_child_name].parent = empty
|
|
||||||
else:
|
|
||||||
b_obj.parent = empty
|
b_obj.parent = empty
|
||||||
|
else:
|
||||||
|
bpy.data.objects[obj_child_name].parent = empty
|
||||||
obj_child_name = obj_parent.Label
|
obj_child_name = obj_parent.Label
|
||||||
obj_parent = obj_parent.getParentGeoFeatureGroup()
|
obj_parent = obj_parent.getParentGeoFeatureGroup()
|
||||||
empty.select_set(False)
|
empty.select_set(False)
|
||||||
logger.debug('Add parent %s to object %s', empty.name, b_obj.name)
|
logger.debug('Add parent %s to object %s', empty.name, b_obj.name)
|
||||||
|
|
||||||
|
|
78
cg/blender/import_fcstd/import_materials.py
Normal file
78
cg/blender/import_fcstd/import_materials.py
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (C) 2023 Ilia Kurochkin <brothermechanic@gmail.com>
|
||||||
|
#
|
||||||
|
# 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 3 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.
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import bpy
|
||||||
|
from bpy_extras.node_shader_utils import PrincipledBSDFWrapper
|
||||||
|
from utils.shininess_to_roughness import shiny_to_rough
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
|
def import_materials(bobj, fem_mat):
|
||||||
|
""" Build Blender Shader from FreeCAD's FEM material """
|
||||||
|
fem_mat_name = fem_mat.Material['Name']
|
||||||
|
|
||||||
|
if fem_mat_name in bpy.data.materials:
|
||||||
|
if len(bobj.material_slots) < 1:
|
||||||
|
bobj.data.materials.append(bpy.data.materials[fem_mat_name])
|
||||||
|
else:
|
||||||
|
bobj.material_slots[0].material = bpy.data.materials[fem_mat_name]
|
||||||
|
else:
|
||||||
|
if 'DiffuseColor' in fem_mat.Material.keys():
|
||||||
|
d_col_str = fem_mat.Material['DiffuseColor']
|
||||||
|
d_col4 = tuple(
|
||||||
|
map(float, d_col_str[1:-1].split(', ')))
|
||||||
|
d_col = d_col4[:-1]
|
||||||
|
else:
|
||||||
|
d_col = (0.5, 0.5, 0.5)
|
||||||
|
if 'Father' in fem_mat.Material.keys():
|
||||||
|
if fem_mat.Material['Father'] == 'Metal':
|
||||||
|
me = 1
|
||||||
|
else:
|
||||||
|
me = 0
|
||||||
|
else:
|
||||||
|
me = 0
|
||||||
|
if 'Shininess' in fem_mat.Material.keys():
|
||||||
|
shiny = float(fem_mat.Material['Shininess'])
|
||||||
|
if shiny == 0:
|
||||||
|
rg = 0.5
|
||||||
|
else:
|
||||||
|
rg = shiny_to_rough(shiny)
|
||||||
|
else:
|
||||||
|
rg = 0.5
|
||||||
|
if 'EmissiveColor' in fem_mat.Material.keys():
|
||||||
|
e_col_str = fem_mat.Material['EmissiveColor']
|
||||||
|
e_col4 = tuple(
|
||||||
|
map(float, e_col_str[1:-1].split(', ')))
|
||||||
|
e_col = e_col4[:-1]
|
||||||
|
else:
|
||||||
|
e_col = (0.0, 0.0, 0.0)
|
||||||
|
if 'Transparency' in fem_mat.Material.keys():
|
||||||
|
tr_str = fem_mat.Material['Transparency']
|
||||||
|
alpha = 1.0 - float(tr_str)
|
||||||
|
else:
|
||||||
|
alpha = 1.0
|
||||||
|
|
||||||
|
bmat = bpy.data.materials.new(name=fem_mat_name)
|
||||||
|
bmat.use_nodes = True
|
||||||
|
principled = PrincipledBSDFWrapper(bmat, is_readonly=False)
|
||||||
|
principled.base_color = d_col
|
||||||
|
principled.metallic = me
|
||||||
|
principled.roughness = rg
|
||||||
|
principled.emission_color = e_col
|
||||||
|
principled.alpha = alpha
|
||||||
|
bobj.data.materials.append(bmat)
|
||||||
|
|
||||||
|
logger.debug('Assign %s to object %s', fem_mat_name, bobj.name)
|
|
@ -1,78 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright (C) 2023 Ilia Kurochkin <brothermechanic@gmail.com>
|
|
||||||
#
|
|
||||||
# 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 3 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.
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
import bpy
|
|
||||||
from bpy_extras.node_shader_utils import PrincipledBSDFWrapper
|
|
||||||
from utils.shininess_to_roughness import shiny_to_rough
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
|
|
||||||
|
|
||||||
def set_fem_mat(obj, bobj, fem_mat):
|
|
||||||
if fem_mat.isDerivedFrom("App::MaterialObjectPython"):
|
|
||||||
if fem_mat.References[0][0].Name == obj.Name:
|
|
||||||
fem_mat_name = fem_mat.Material['Name']
|
|
||||||
if 'DiffuseColor' in fem_mat.Material.keys():
|
|
||||||
d_col_str = fem_mat.Material['DiffuseColor']
|
|
||||||
d_col4 = tuple(
|
|
||||||
map(float, d_col_str[1:-1].split(', ')))
|
|
||||||
d_col = d_col4[:-1]
|
|
||||||
else:
|
|
||||||
d_col = (0.5, 0.5, 0.5)
|
|
||||||
if 'Father' in fem_mat.Material.keys():
|
|
||||||
if fem_mat.Material['Father'] == 'Metal':
|
|
||||||
me = 1
|
|
||||||
else:
|
|
||||||
me = 0
|
|
||||||
else:
|
|
||||||
me = 0
|
|
||||||
if 'Shininess' in fem_mat.Material.keys():
|
|
||||||
shiny = float(fem_mat.Material['Shininess'])
|
|
||||||
if shiny == 0:
|
|
||||||
rg = 0.5
|
|
||||||
else:
|
|
||||||
rg = shiny_to_rough(shiny)
|
|
||||||
else:
|
|
||||||
rg = 0.5
|
|
||||||
if 'EmissiveColor' in fem_mat.Material.keys():
|
|
||||||
e_col_str = fem_mat.Material['EmissiveColor']
|
|
||||||
e_col4 = tuple(
|
|
||||||
map(float, e_col_str[1:-1].split(', ')))
|
|
||||||
e_col = e_col4[:-1]
|
|
||||||
else:
|
|
||||||
e_col = (0.0, 0.0, 0.0)
|
|
||||||
if 'Transparency' in fem_mat.Material.keys():
|
|
||||||
tr_str = fem_mat.Material['Transparency']
|
|
||||||
alpha = 1.0 - float(tr_str)
|
|
||||||
else:
|
|
||||||
alpha = 1.0
|
|
||||||
|
|
||||||
logger.debug('Assign %s to object %s', fem_mat_name, obj.Label)
|
|
||||||
|
|
||||||
if fem_mat_name in bpy.data.materials:
|
|
||||||
if len(bobj.material_slots) < 1:
|
|
||||||
bobj.data.materials.append(bpy.data.materials[fem_mat_name])
|
|
||||||
else:
|
|
||||||
bobj.material_slots[0].material = bpy.data.materials[fem_mat_name]
|
|
||||||
else:
|
|
||||||
bmat = bpy.data.materials.new(name=fem_mat_name)
|
|
||||||
bmat.use_nodes = True
|
|
||||||
principled = PrincipledBSDFWrapper(bmat, is_readonly=False)
|
|
||||||
principled.base_color = d_col
|
|
||||||
principled.metallic = me
|
|
||||||
principled.roughness = rg
|
|
||||||
principled.emission_color = e_col
|
|
||||||
principled.alpha = alpha
|
|
||||||
bobj.data.materials.append(bmat)
|
|
|
@ -43,7 +43,7 @@ def asset_setup(transforms=True, sharpness=True, shading=True):
|
||||||
bpy.ops.object.mode_set(mode='EDIT')
|
bpy.ops.object.mode_set(mode='EDIT')
|
||||||
bpy.ops.mesh.select_all(action='DESELECT')
|
bpy.ops.mesh.select_all(action='DESELECT')
|
||||||
bpy.ops.mesh.select_mode(type='EDGE')
|
bpy.ops.mesh.select_mode(type='EDGE')
|
||||||
bpy.ops.mesh.edges_select_sharp( sharpness = math.radians(30) )
|
bpy.ops.mesh.edges_select_sharp( sharpness = math.radians(10) )
|
||||||
bpy.ops.mesh.mark_sharp()
|
bpy.ops.mesh.mark_sharp()
|
||||||
bpy.ops.mesh.select_all(action='SELECT')
|
bpy.ops.mesh.select_all(action='SELECT')
|
||||||
bpy.ops.uv.smart_project()
|
bpy.ops.uv.smart_project()
|
||||||
|
@ -57,9 +57,6 @@ def asset_setup(transforms=True, sharpness=True, shading=True):
|
||||||
bpy.context.view_layer.objects.active.modifiers["decimate"].decimate_type = "DISSOLVE"
|
bpy.context.view_layer.objects.active.modifiers["decimate"].decimate_type = "DISSOLVE"
|
||||||
bpy.context.view_layer.objects.active.modifiers["decimate"].angle_limit = 0.00872665
|
bpy.context.view_layer.objects.active.modifiers["decimate"].angle_limit = 0.00872665
|
||||||
bpy.context.object.modifiers["decimate"].show_expanded = 0
|
bpy.context.object.modifiers["decimate"].show_expanded = 0
|
||||||
bpy.context.view_layer.objects.active.modifiers.new(type='WEIGHTED_NORMAL', name='weightednormal')
|
|
||||||
bpy.context.view_layer.objects.active.modifiers["weightednormal"].keep_sharp = 1
|
|
||||||
bpy.context.object.modifiers["weightednormal"].show_expanded = 0
|
|
||||||
bpy.context.view_layer.objects.active.modifiers.new(type='TRIANGULATE', name='triangulate')
|
bpy.context.view_layer.objects.active.modifiers.new(type='TRIANGULATE', name='triangulate')
|
||||||
bpy.context.object.modifiers["triangulate"].keep_custom_normals = 1
|
bpy.context.object.modifiers["triangulate"].keep_custom_normals = 1
|
||||||
bpy.context.object.modifiers["triangulate"].show_expanded = 0
|
bpy.context.object.modifiers["triangulate"].show_expanded = 0
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
; Robossembler_ABS-Dark-Rough
|
||||||
|
; (c) 2023 brothermechanic (CC-BY 3.0)
|
||||||
|
|
||||||
|
[General]
|
||||||
|
Name = Robossembler_ABS-Dark-Rough
|
||||||
|
Description = Generic ABS material for Robossembler project's pipeline.
|
||||||
|
Father = Thermoplast
|
||||||
|
|
||||||
|
[Mechanical]
|
||||||
|
Density = 1060 kg/m^3
|
||||||
|
PoissonRatio = 0.37
|
||||||
|
UltimateTensileStrength = 38.8 MPa
|
||||||
|
YieldStrength = 44.1 MPa
|
||||||
|
YoungsModulus = 2300 MPa
|
||||||
|
|
||||||
|
[Thermal]
|
||||||
|
SpecificHeat = 2050 J/kg/K
|
||||||
|
ThermalConductivity = 0.158 W/m/K
|
||||||
|
ThermalExpansionCoefficient = 0.000093 m/m/K
|
||||||
|
|
||||||
|
[Rendering]
|
||||||
|
DiffuseColor = (0.1, 0.1, 0.1, 1.0)
|
||||||
|
EmissiveColor = (0.0, 0.0, 0.0, 1.0)
|
||||||
|
Shininess = 35
|
||||||
|
TexturePath = ~/texture.jpg
|
||||||
|
Transparency = 0.0
|
||||||
|
|
||||||
|
[VectorRendering]
|
||||||
|
ViewColor = (0.0, 0.0, 1.0, 1.0)
|
||||||
|
|
||||||
|
[UserDefined]
|
||||||
|
usernum = 0.0
|
||||||
|
userstr = String
|
|
@ -1,8 +1,8 @@
|
||||||
; Robossembler_ABS-Grey-Rough
|
; Robossembler_ABS-White-Rough
|
||||||
; (c) 2023 brothermechanic (CC-BY 3.0)
|
; (c) 2023 brothermechanic (CC-BY 3.0)
|
||||||
|
|
||||||
[General]
|
[General]
|
||||||
Name = Robossembler_ABS-Grey-Rough
|
Name = Robossembler_ABS-White-Rough
|
||||||
Description = Generic ABS material for Robossembler project's pipeline.
|
Description = Generic ABS material for Robossembler project's pipeline.
|
||||||
Father = Thermoplast
|
Father = Thermoplast
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,8 @@ logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
def freecad_asset_pipeline(fcstd_path,
|
def freecad_asset_pipeline(fcstd_path,
|
||||||
tessellation,
|
linear_deflection,
|
||||||
|
angular_deflection,
|
||||||
mesh_export_path=None,
|
mesh_export_path=None,
|
||||||
json_path=None,
|
json_path=None,
|
||||||
blend_path=None,
|
blend_path=None,
|
||||||
|
@ -38,7 +39,7 @@ def freecad_asset_pipeline(fcstd_path,
|
||||||
cleanup_orphan_data()
|
cleanup_orphan_data()
|
||||||
|
|
||||||
# import objects
|
# import objects
|
||||||
obj_importer(fcstd_path, tessellation)
|
obj_importer(fcstd_path, linear_deflection, angular_deflection)
|
||||||
|
|
||||||
# import lcs
|
# import lcs
|
||||||
if json_path is None:
|
if json_path is None:
|
||||||
|
@ -80,7 +81,9 @@ if __name__ == '__main__':
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--fcstd_path', type=str, help='Path to source FreeCAD scene', required=True)
|
'--fcstd_path', type=str, help='Path to source FreeCAD scene', required=True)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--tessellation', type=int, help='Tessellation number', default=10, required=False)
|
'--linear_deflection', type=float, help='Max linear distance error', default=0.1, required=False)
|
||||||
|
parser.add_argument(
|
||||||
|
'--angular_deflection', type=float, help='Max angular distance error', default=20.0, required=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--mesh_export_path', type=str, help='Path for export meshes', required=False)
|
'--mesh_export_path', type=str, help='Path for export meshes', required=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
|
@ -92,7 +95,8 @@ if __name__ == '__main__':
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
freecad_asset_pipeline(args.fcstd_path,
|
freecad_asset_pipeline(args.fcstd_path,
|
||||||
args.tessellation,
|
args.linear_deflection,
|
||||||
|
args.angular_deflection,
|
||||||
args.mesh_export_path,
|
args.mesh_export_path,
|
||||||
args.json_path,
|
args.json_path,
|
||||||
args.blend_path,
|
args.blend_path,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue