[Blender 3.6] UV Packer
This commit is contained in:
parent
80e3c58913
commit
78e31ea49c
18 changed files with 635 additions and 347 deletions
|
@ -35,10 +35,10 @@ logger = logging.getLogger(__name__)
|
|||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
# COLLECTIONS NAMIG CONVENTION
|
||||
part_col_name = 'Part'
|
||||
part_col_name = 'Parts'
|
||||
lcs_col_name = 'LCS'
|
||||
hierarchy_col_name = 'Hierarchy'
|
||||
lowpoly_col_name = 'Lowpoly Parts'
|
||||
lowpoly_col_name = 'Lowpoly'
|
||||
# LCS POINT'S SUFFIXES CONVENTION
|
||||
inlet = '_in'
|
||||
outlet = '_out'
|
||||
|
@ -91,8 +91,6 @@ def json_to_blend(js_data):
|
|||
bmesh.update()
|
||||
bobj = bpy.data.objects.new(js_obj, bmesh)
|
||||
part_collection.objects.link(bobj)
|
||||
else:
|
||||
logger.info('%s has not mesh data!', js_obj)
|
||||
|
||||
if bobj:
|
||||
fc_placement(bobj,
|
||||
|
@ -142,5 +140,5 @@ def json_to_blend(js_data):
|
|||
|
||||
# TODO
|
||||
# update do not dork
|
||||
logger.info('Imported %s objects without errors', len(bobjs))
|
||||
logger.info('Generated %s objects without errors', len(bobjs))
|
||||
return bobjs_for_render
|
||||
|
|
|
@ -17,7 +17,6 @@ Basic mesh processing for asset pipeline.
|
|||
__version__ = '0.2'
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import bpy
|
||||
import math
|
||||
|
||||
|
@ -51,7 +50,7 @@ def setup_meshes(bobjs, cleanup=False, sharpness=False, shading=False):
|
|||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
bpy.ops.mesh.select_mode(type='EDGE')
|
||||
bpy.ops.mesh.edges_select_sharp( sharpness=math.radians(12) )
|
||||
bpy.ops.mesh.edges_select_sharp(sharpness=math.radians(12))
|
||||
bpy.ops.mesh.mark_sharp()
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.uv.smart_project()
|
140
cg/blender/processing/lowpoly_setup.py
Normal file
140
cg/blender/processing/lowpoly_setup.py
Normal file
|
@ -0,0 +1,140 @@
|
|||
# -*- 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.
|
||||
'''
|
||||
DESCRIPTION.
|
||||
Create lowpoly shells from parts collections.
|
||||
'''
|
||||
__version__ = '0.1'
|
||||
|
||||
import logging
|
||||
import bpy
|
||||
|
||||
from blender.utils.generative_modifiers import shell_remesher
|
||||
from blender.utils.object_converter import mesh_to_mesh
|
||||
from blender.utils.object_relations import parenting
|
||||
from blender.utils.mesh_tools import select_peaks, select_stratched_edges
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
|
||||
# COLLECTIONS NAMIG CONVENTION
|
||||
parts_col_name = 'Parts'
|
||||
lcs_col_name = 'LCS'
|
||||
hierarchy_col_name = 'Hierarchy'
|
||||
lowpoly_col_name = 'Lowpoly'
|
||||
# LCS POINT'S SUFFIXES CONVENTION
|
||||
inlet = '_in'
|
||||
outlet = '_out'
|
||||
root = '_root'
|
||||
# CG ASSETS SUFFIXES CONVENTION
|
||||
hightpoly = '_hp'
|
||||
lowpoly = '_lp'
|
||||
render = '_render'
|
||||
|
||||
|
||||
def parts_to_shells(hightpoly_part_names):
|
||||
''' Create lowpoly shells from parts collections. '''
|
||||
logger.info('Lowpoly shells creation launched...')
|
||||
|
||||
lowpoly_col = bpy.data.collections.new(lowpoly_col_name)
|
||||
bpy.context.scene.collection.children.link(lowpoly_col)
|
||||
|
||||
for part_name in hightpoly_part_names:
|
||||
# generate lowpoly objects from part collections
|
||||
lowpoly_name = ('{}{}'.format(part_name, lowpoly))
|
||||
lowpoly_mesh = bpy.data.meshes.new(lowpoly_name)
|
||||
lowpoly_obj = bpy.data.objects.new(lowpoly_name, lowpoly_mesh)
|
||||
bpy.context.view_layer.update()
|
||||
part_inlet = bpy.data.objects.get('{}{}'.format(part_name, inlet))
|
||||
lowpoly_obj.matrix_world = part_inlet.matrix_world.copy()
|
||||
parenting(part_inlet, lowpoly_obj)
|
||||
lowpoly_col.objects.link(lowpoly_obj)
|
||||
|
||||
shell_remesher(lowpoly_obj, 'remesh_nodes', 'robossembler')
|
||||
part_col = bpy.data.collections[('{}{}'.format(part_name, hightpoly))]
|
||||
lowpoly_obj.modifiers['remesh_nodes']['Input_0'] = part_col
|
||||
|
||||
remesh_voxel = lowpoly_obj.modifiers.new('remesh_voxel', type='REMESH')
|
||||
remesh_voxel.mode = 'VOXEL'
|
||||
remesh_voxel.voxel_size = 0.001
|
||||
|
||||
remesh_sharp = lowpoly_obj.modifiers.new('remesh_sharp', type='REMESH')
|
||||
remesh_sharp.mode = 'SHARP'
|
||||
remesh_sharp.octree_depth = 7
|
||||
|
||||
decimate = lowpoly_obj.modifiers.new('decimate', type='DECIMATE')
|
||||
decimate.decimate_type = 'COLLAPSE'
|
||||
decimate.ratio = 0.1
|
||||
|
||||
# apply all modifiers to mesh
|
||||
parenting(part_inlet, mesh_to_mesh(lowpoly_obj))
|
||||
|
||||
# fix non_manifold shape
|
||||
for lowpoly_obj in lowpoly_col.objects:
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
lowpoly_obj.select_set(state=True)
|
||||
bpy.context.view_layer.objects.active = lowpoly_obj
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
# pass 1
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
|
||||
select_peaks(lowpoly_obj.data)
|
||||
bpy.ops.mesh.select_non_manifold()
|
||||
bpy.ops.mesh.dissolve_mode(use_verts=True, use_boundary_tear=False)
|
||||
bpy.ops.mesh.delete(type='VERT')
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.mesh.quads_convert_to_tris(quad_method='BEAUTY', ngon_method='BEAUTY')
|
||||
# pass 2
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
|
||||
select_peaks(lowpoly_obj.data)
|
||||
bpy.ops.mesh.select_non_manifold()
|
||||
bpy.ops.mesh.dissolve_mode(use_verts=True, use_boundary_tear=False)
|
||||
bpy.ops.mesh.delete(type='VERT')
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.mesh.quads_convert_to_tris(quad_method='BEAUTY', ngon_method='BEAUTY')
|
||||
# pass 3
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
|
||||
select_peaks(lowpoly_obj.data)
|
||||
bpy.ops.mesh.select_non_manifold()
|
||||
bpy.ops.mesh.dissolve_mode(use_verts=True, use_boundary_tear=False)
|
||||
bpy.ops.mesh.delete(type='VERT')
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.mesh.quads_convert_to_tris(quad_method='BEAUTY', ngon_method='BEAUTY')
|
||||
# pass 4
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
|
||||
select_peaks(lowpoly_obj.data)
|
||||
bpy.ops.mesh.select_non_manifold()
|
||||
bpy.ops.mesh.dissolve_mode(use_verts=True, use_boundary_tear=False)
|
||||
bpy.ops.mesh.delete(type='VERT')
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.mesh.quads_convert_to_tris(quad_method='BEAUTY', ngon_method='BEAUTY')
|
||||
# pass 5
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
bpy.ops.mesh.select_mode(type='EDGE')
|
||||
select_stratched_edges(lowpoly_obj.data)
|
||||
bpy.ops.mesh.dissolve_mode(use_verts=True)
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.mesh.quads_convert_to_tris(quad_method='BEAUTY', ngon_method='BEAUTY')
|
||||
bpy.ops.mesh.normals_make_consistent()
|
||||
# final
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
bpy.ops.mesh.select_mode(type='FACE')
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
logger.info('Lowpoly shells created successfully!')
|
||||
|
||||
return lowpoly_col.objects
|
|
@ -31,10 +31,10 @@ logging.basicConfig(level=logging.INFO)
|
|||
|
||||
|
||||
# COLLECTIONS NAMIG CONVENTION
|
||||
parts_col_name = 'Import Parts'
|
||||
lcs_col_name = 'Import LCS'
|
||||
hierarchy_col_name = 'Import Hierarchy'
|
||||
lowpoly_col_name = 'Lowpoly Parts'
|
||||
parts_col_name = 'Parts'
|
||||
lcs_col_name = 'LCS'
|
||||
hierarchy_col_name = 'Hierarchy'
|
||||
lowpoly_col_name = 'Lowpoly'
|
||||
# LCS POINT'S SUFFIXES CONVENTION
|
||||
inlet = '_in'
|
||||
outlet = '_out'
|
64
cg/blender/processing/uv_setup.py
Normal file
64
cg/blender/processing/uv_setup.py
Normal file
|
@ -0,0 +1,64 @@
|
|||
# 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.
|
||||
'''
|
||||
DESCRIPTION.
|
||||
UV unwrapping and UV packing processing.
|
||||
'''
|
||||
__version__ = '0.1'
|
||||
|
||||
import logging
|
||||
import math
|
||||
import bpy
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
|
||||
def uv_unwrap(objs, angle_limit=30):
|
||||
''' UV unwrapping and UV packing processing '''
|
||||
for obj in objs:
|
||||
obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
|
||||
# unwrapping
|
||||
bpy.ops.uv.smart_project(angle_limit=math.radians(angle_limit))
|
||||
|
||||
# packing
|
||||
bpy.ops.uv.pack_islands(udim_source='CLOSEST_UDIM',
|
||||
rotate=True,
|
||||
rotate_method='ANY',
|
||||
scale=True,
|
||||
merge_overlap=False,
|
||||
margin_method='ADD',
|
||||
margin=(1 / 256),
|
||||
pin=False,
|
||||
pin_method='LOCKED',
|
||||
shape_method='CONCAVE')
|
||||
bpy.ops.uv.pack_islands(udim_source='CLOSEST_UDIM',
|
||||
rotate=True,
|
||||
rotate_method='ANY',
|
||||
scale=True,
|
||||
merge_overlap=False,
|
||||
margin_method='ADD',
|
||||
margin=(1 / 256),
|
||||
pin=False,
|
||||
pin_method='LOCKED',
|
||||
shape_method='CONCAVE')
|
||||
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
obj.select_set(False)
|
||||
|
||||
logger.info('UV unwrapping and UV packing processing done successfully!')
|
||||
return objs
|
|
@ -1,86 +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.
|
||||
'''
|
||||
DESCRIPTION.
|
||||
Create lowpoly shells from parts collections.
|
||||
'''
|
||||
__version__ = '0.1'
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import bpy
|
||||
import math
|
||||
|
||||
from blender.utils.generative_modifiers import shell_remesher
|
||||
from blender.utils.object_converter import convert_mesh_to_mesh
|
||||
from blender.utils.object_relations import parenting
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
|
||||
# COLLECTIONS NAMIG CONVENTION
|
||||
parts_col_name = 'Import Parts'
|
||||
lcs_col_name = 'Import LCS'
|
||||
hierarchy_col_name = 'Import Hierarchy'
|
||||
lowpoly_col_name = 'Lowpoly Parts'
|
||||
# LCS POINT'S SUFFIXES CONVENTION
|
||||
inlet = '_in'
|
||||
outlet = '_out'
|
||||
root = '_root'
|
||||
# CG ASSETS SUFFIXES CONVENTION
|
||||
hightpoly = '_hp'
|
||||
lowpoly = '_lp'
|
||||
render = '_render'
|
||||
|
||||
|
||||
def parts_to_shells(hightpoly_part_names):
|
||||
''' Create lowpoly shells from parts collections. '''
|
||||
logger.info('Lowpoly shells creation launched...')
|
||||
|
||||
lowpoly_col = bpy.data.collections.new(lowpoly_col_name)
|
||||
bpy.context.scene.collection.children.link(lowpoly_col)
|
||||
|
||||
for part_name in hightpoly_part_names:
|
||||
# generate lowpoly objects from part collections
|
||||
lowpoly_name = ('{}{}'.format(part_name, lowpoly))
|
||||
lowpoly_mesh = bpy.data.meshes.new(lowpoly_name)
|
||||
lowpoly_obj = bpy.data.objects.new(lowpoly_name, lowpoly_mesh)
|
||||
bpy.context.view_layer.update()
|
||||
part_inlet = bpy.data.objects.get('{}{}'.format(part_name, inlet))
|
||||
lowpoly_obj.matrix_world = part_inlet.matrix_world.copy()
|
||||
parenting(part_inlet, lowpoly_obj)
|
||||
lowpoly_col.objects.link(lowpoly_obj)
|
||||
|
||||
shell_remesher(lowpoly_obj, 'remesh_nodes', 'robossembler')
|
||||
part_col = bpy.data.collections[('{}{}'.format(part_name, hightpoly))]
|
||||
lowpoly_obj.modifiers['remesh_nodes']['Input_0'] = part_col
|
||||
|
||||
remesh_voxel = lowpoly_obj.modifiers.new('remesh_voxel', type='REMESH')
|
||||
remesh_voxel.mode = 'VOXEL'
|
||||
remesh_voxel.voxel_size = 0.001
|
||||
|
||||
remesh_sharp = lowpoly_obj.modifiers.new('remesh_sharp', type='REMESH')
|
||||
remesh_sharp.mode = 'SHARP'
|
||||
remesh_sharp.octree_depth = 7
|
||||
|
||||
decimate = lowpoly_obj.modifiers.new('decimate', type='DECIMATE')
|
||||
decimate.decimate_type = 'COLLAPSE'
|
||||
decimate.ratio = 0.1
|
||||
|
||||
# apply all modifiers to mesh
|
||||
parenting(part_inlet, convert_mesh_to_mesh(lowpoly_obj))
|
||||
|
||||
logger.info('Lowpoly shells created successfully!')
|
||||
|
||||
return lowpoly_col.objects
|
|
@ -2,6 +2,7 @@
|
|||
"""
|
||||
DESCRIPTION.
|
||||
This script setup render settings for reduce rendertime!
|
||||
DEPRECATED
|
||||
"""
|
||||
__version__ = "0.1"
|
||||
|
||||
|
|
83
cg/blender/utils/mesh_tools.py
Normal file
83
cg/blender/utils/mesh_tools.py
Normal file
|
@ -0,0 +1,83 @@
|
|||
# -*- 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.
|
||||
'''
|
||||
DESCRIPTION.
|
||||
Various mesh tools for Edit Mode.
|
||||
'''
|
||||
__version__ = '0.1'
|
||||
|
||||
import bmesh
|
||||
from math import radians
|
||||
|
||||
|
||||
def select_peaks(me, peak_limit_angle=60, peak_accuracy_angle=10):
|
||||
''' Select sharp vertices that stand alone. '''
|
||||
bm = bmesh.from_edit_mesh(me)
|
||||
|
||||
def is_sharp(vert, eps=radians(peak_limit_angle)):
|
||||
sharps = []
|
||||
face_before = None
|
||||
for face in vert.link_faces:
|
||||
if face_before:
|
||||
face_angle = face.normal.angle(face_before.normal)
|
||||
if face_angle > radians(peak_accuracy_angle):
|
||||
angle = vert.normal.angle(face.normal)
|
||||
if angle > eps:
|
||||
sharps.append(angle)
|
||||
face_before = face
|
||||
return (
|
||||
(len(sharps) + 1) == len(vert.link_faces)
|
||||
or (len(sharps) + 2) == len(vert.link_faces)
|
||||
)
|
||||
|
||||
def non_single(vert):
|
||||
for edge in vert.link_edges:
|
||||
if edge.other_vert(vert).select:
|
||||
return False
|
||||
return True
|
||||
|
||||
for v in bm.verts:
|
||||
v.select_set(
|
||||
is_sharp(v)
|
||||
)
|
||||
|
||||
for v in bm.verts:
|
||||
if v.select:
|
||||
v.select_set(
|
||||
non_single(v)
|
||||
)
|
||||
|
||||
bmesh.update_edit_mesh(me)
|
||||
return me
|
||||
|
||||
|
||||
def select_zero_faces(me):
|
||||
''' Select very small faces. '''
|
||||
bm = bmesh.from_edit_mesh(me)
|
||||
[f.select_set(True) for f in bm.faces if f.calc_area() < 1e-7]
|
||||
bmesh.update_edit_mesh(me)
|
||||
return me
|
||||
|
||||
|
||||
def select_stratched_edges(me, edge_length_limit=0.002):
|
||||
''' Select very stratched edges of small faces. '''
|
||||
bm = bmesh.from_edit_mesh(me)
|
||||
faces_stratched = [f for f in bm.faces if f.calc_area() < 1e-6]
|
||||
for face in faces_stratched:
|
||||
edges_lengths = {e: e.calc_length() for e in face.edges}
|
||||
edge_max_length = max(edges_lengths.values())
|
||||
if edge_max_length > edge_length_limit:
|
||||
edge_max = [k for k, v in edges_lengths.items() if v == edge_max_length][0]
|
||||
edge_max.select_set(True)
|
||||
bmesh.update_edit_mesh(me)
|
||||
return me
|
|
@ -19,7 +19,7 @@ __version__ = '0.1'
|
|||
import bpy
|
||||
|
||||
|
||||
def convert_mesh_to_mesh(obj):
|
||||
def mesh_to_mesh(obj):
|
||||
''' Convert all deformers and modifiers of object to it's mesh. '''
|
||||
if obj and obj.type == 'MESH':
|
||||
deg = bpy.context.evaluated_depsgraph_get()
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
# coding: utf-8
|
||||
#!/usr/bin/env python
|
||||
# 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 subprocess
|
||||
import json
|
||||
|
||||
|
||||
def freecad_proc(*args, **kwargs):
|
||||
command = [
|
||||
args[0],
|
||||
args[1],
|
||||
|
||||
# general property
|
||||
kwargs['filename'],
|
||||
kwargs['tesselation_method'],
|
||||
kwargs['linear_deflection'],
|
||||
kwargs['angular_deflection'],
|
||||
kwargs['fem_size'],
|
||||
kwargs['skiphidden'],
|
||||
kwargs['nonsolid_property'],
|
||||
]
|
||||
|
||||
proc = subprocess.run(command,
|
||||
check=True,
|
||||
stdout=subprocess.PIPE,
|
||||
encoding='utf-8')
|
||||
|
||||
return json.loads(proc.stdout.split('FreeCAD ')[0])
|
||||
|
||||
|
||||
kwargs = {}
|
||||
kwargs['filename'] = '/<path to .FCStd>'
|
||||
kwargs['tesselation_method'] = 'Standard'
|
||||
kwargs['linear_deflection'] = '0.1'
|
||||
kwargs['angular_deflection'] = '30.0'
|
||||
kwargs['fem_size'] = '10.0'
|
||||
kwargs['skiphidden'] = 'True'
|
||||
kwargs['nonsolid_property'] = 'Robossembler_NonSolid'
|
||||
|
||||
js_data = freecad_proc(
|
||||
'freecadcmd',
|
||||
'/<path to>/cg/freecad/utils/export_freecad_scene.py',
|
||||
**kwargs)
|
||||
|
||||
print(js_data)
|
|
@ -24,26 +24,20 @@ import Mesh
|
|||
import MeshPart
|
||||
import logging
|
||||
import math
|
||||
import sys
|
||||
from freecad.utils.is_object_solid import is_object_solid
|
||||
from utils.custom_parser import CustomArgumentParser
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
|
||||
def freecad_to_json(filename,
|
||||
tesselation_method='Standard',
|
||||
linear_deflection=0.1,
|
||||
angular_deflection=30.0,
|
||||
fem_size=50.0,
|
||||
skiphidden=True,
|
||||
nonsolid_property='Robossembler_NonSolid'):
|
||||
def freecad_to_json(**kwargs):
|
||||
''' Reads a FreeCAD .FCStd file and return json assembly. '''
|
||||
|
||||
scene = {}
|
||||
js_objs = {}
|
||||
|
||||
doc = FreeCAD.open(filename)
|
||||
doc = FreeCAD.open(kwargs['fcstd_path'])
|
||||
docname = doc.Name
|
||||
|
||||
# collect all materials
|
||||
|
@ -55,7 +49,7 @@ def freecad_to_json(filename,
|
|||
for obj in doc.Objects:
|
||||
js_obj = {}
|
||||
|
||||
if skiphidden:
|
||||
if kwargs['skiphidden']:
|
||||
if not obj.Visibility:
|
||||
continue
|
||||
|
||||
|
@ -66,26 +60,26 @@ def freecad_to_json(filename,
|
|||
js_obj['type'] = 'PART'
|
||||
# filter for nonsolids
|
||||
|
||||
if is_object_solid(obj) or hasattr(obj, nonsolid_property):
|
||||
if is_object_solid(obj) or hasattr(obj, kwargs['property_forse_nonsolid']):
|
||||
# create mesh from shape
|
||||
shape = obj.Shape
|
||||
shape = obj.Shape.copy()
|
||||
shape.Placement = obj.Placement.inverse().multiply(shape.Placement)
|
||||
meshfromshape = doc.addObject('Mesh::Feature', 'Mesh')
|
||||
if tesselation_method == 'Standard':
|
||||
if kwargs['tesselation_method'] == 'Standard':
|
||||
meshfromshape.Mesh = MeshPart.meshFromShape(
|
||||
Shape=shape,
|
||||
LinearDeflection=linear_deflection,
|
||||
AngularDeflection=math.radians(angular_deflection),
|
||||
LinearDeflection=kwargs['linear_deflection'],
|
||||
AngularDeflection=math.radians(kwargs['angular_deflection']),
|
||||
Relative=False)
|
||||
elif tesselation_method == 'FEM':
|
||||
elif kwargs['tesselation_method'] == 'FEM':
|
||||
meshfromshape.Mesh = MeshPart.meshFromShape(
|
||||
Shape=shape,
|
||||
MaxLength=fem_size)
|
||||
MaxLength=kwargs['fem_size'])
|
||||
else:
|
||||
raise TypeError('Wrong tesselation method! '
|
||||
'Standard and FEM methods are supported only!')
|
||||
break
|
||||
|
||||
t = meshfromshape.Mesh.Topology
|
||||
verts = [[v.x, v.y, v.z] for v in t[0]]
|
||||
faces = t[1]
|
||||
|
@ -128,18 +122,66 @@ def freecad_to_json(filename,
|
|||
|
||||
FreeCAD.closeDocument(docname)
|
||||
|
||||
scene[filename] = js_objs
|
||||
scene[kwargs['fcstd_path']] = js_objs
|
||||
|
||||
logger.info('Stored %s objects without errors', len(js_objs))
|
||||
logger.info('Passed %s objects without errors', len(js_objs))
|
||||
|
||||
print(json.dumps(scene))
|
||||
|
||||
|
||||
args = sys.argv[2:]
|
||||
for arg in args[2:5]:
|
||||
args[args.index(arg)] = float(arg)
|
||||
for arg in args[5:6]:
|
||||
args[args.index(arg)] = bool(arg)
|
||||
#for num, item in enumerate(args):
|
||||
# print(num, type(item))
|
||||
freecad_to_json(*args)
|
||||
# to run script via FreeCADCmd
|
||||
parser = CustomArgumentParser()
|
||||
parser.add_argument(
|
||||
'--fcstd_path',
|
||||
type=str,
|
||||
help='Path to source FreeCAD scene',
|
||||
required=True
|
||||
)
|
||||
parser.add_argument(
|
||||
'--tesselation_method',
|
||||
type=str,
|
||||
help='Select tesselation method: Standard or FEM.',
|
||||
default='Standard',
|
||||
required=False
|
||||
)
|
||||
parser.add_argument(
|
||||
'--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(
|
||||
'--fem_size',
|
||||
type=float,
|
||||
help='For FEM method only! Finite element size in mm',
|
||||
default=50.0,
|
||||
required=False
|
||||
)
|
||||
parser.add_argument(
|
||||
'--skiphidden',
|
||||
type=bool,
|
||||
help='Skip processing for hidden FreeCAD objects',
|
||||
default=True,
|
||||
required=False
|
||||
)
|
||||
parser.add_argument(
|
||||
'--property_forse_nonsolid',
|
||||
type=str,
|
||||
help='FreeCAD property to enable processing for nonsolid objects',
|
||||
default='Robossembler_NonSolid',
|
||||
required=False
|
||||
)
|
||||
|
||||
kwargs = vars(parser.parse_known_args()[0])
|
||||
|
||||
freecad_to_json(**kwargs)
|
||||
|
||||
logger.info('FreeCAD scene passed!')
|
|
@ -1,4 +1,4 @@
|
|||
### freecad_to_asset.py
|
||||
### cg_pipeline.py
|
||||
|
||||
Пакетное производство 3д ассетов из объектов CAD сцены.
|
||||
|
||||
|
|
187
cg/pipeline/cg_pipeline.py
Normal file
187
cg/pipeline/cg_pipeline.py
Normal file
|
@ -0,0 +1,187 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#!/usr/bin/env python
|
||||
'''
|
||||
DESCRIPTION.
|
||||
Convert and setup FreeCAD solid objects to 3d assets.
|
||||
Support Blender compiled as a Python Module only!
|
||||
'''
|
||||
__version__ = '0.6'
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
from blender.utils.remove_collections import remove_collections
|
||||
from blender.utils.cleanup_orphan_data import cleanup_orphan_data
|
||||
from utils.cmd_proc import cmd_proc
|
||||
from blender.import_cad.build_blender_scene import json_to_blend
|
||||
from blender.processing.restruct_hierarchy_by_lcs import restruct_hierarchy
|
||||
from blender.processing.highpoly_setup import setup_meshes
|
||||
from blender.processing.lowpoly_setup import parts_to_shells
|
||||
from blender.processing.uv_setup import uv_unwrap
|
||||
import bpy
|
||||
import mathutils
|
||||
# from export.dae import export_dae
|
||||
# from export.collision import export_col_stl
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
'''
|
||||
IMPORT COLLECTIONS NAMIG CONVENTION:
|
||||
Parts - collection for mesh objects
|
||||
LCS - collection for location points
|
||||
Hierarchy - collection for hierarchy locators
|
||||
|
||||
LCS POINT'S SUFFIXES CONVENTION:
|
||||
'_in' - inlet suffix
|
||||
'_out' - outlet suffix
|
||||
'_root' - root suffix
|
||||
|
||||
CG ASSETS SUFFIXES CONVENTION:
|
||||
'_hp' - hightpoly asset (reference baking source)
|
||||
'_lp' - lowpoly asset (prepared for game engines)
|
||||
'_render' - root suffix (prepared for render engines)
|
||||
'''
|
||||
|
||||
# ENV
|
||||
freecadcmd = 'freecadcmd'
|
||||
fcstd_data_script = 'freecad_to_json.py'
|
||||
# COLLECTIONS NAMIG CONVENTION
|
||||
parts_col_name = 'Parts'
|
||||
lcs_col_name = 'LCS'
|
||||
hierarchy_col_name = 'Hierarchy'
|
||||
lowpoly_col_name = 'Lowpoly'
|
||||
# LCS POINT'S SUFFIXES CONVENTION
|
||||
inlet = '_in'
|
||||
outlet = '_out'
|
||||
root = '_root'
|
||||
# CG ASSETS SUFFIXES CONVENTION
|
||||
hightpoly = '_hp'
|
||||
lowpoly = '_lp'
|
||||
render = '_render'
|
||||
|
||||
|
||||
def cg_pipeline(**kwargs):
|
||||
''' CG asset creation pipeline '''
|
||||
|
||||
# prepare blend file
|
||||
remove_collections()
|
||||
cleanup_orphan_data()
|
||||
|
||||
# convert FreeCAD scene to Blender scene
|
||||
objs_for_render = json_to_blend(
|
||||
json.loads(
|
||||
cmd_proc(freecadcmd,
|
||||
fcstd_data_script,
|
||||
'--',
|
||||
**kwargs
|
||||
).split('FreeCAD ')[0]
|
||||
)
|
||||
)
|
||||
|
||||
# restructuring hierarchy by lcs points
|
||||
lcs_objects = restruct_hierarchy()
|
||||
|
||||
# save blender scene
|
||||
if kwargs['blend_path'] is not None:
|
||||
if not os.path.isdir(os.path.dirname(kwargs['blend_path'])):
|
||||
os.makedirs(os.path.dirname(kwargs['blend_path']))
|
||||
bpy.ops.wm.save_as_mainfile(filepath=kwargs['blend_path'])
|
||||
|
||||
# prepare highpoly
|
||||
setup_meshes(objs_for_render, sharpness=True, shading=True)
|
||||
# prepare lowpoly
|
||||
part_names = [p.name.split(inlet)[0] for p in lcs_objects if p.name.endswith(inlet)]
|
||||
lowpoly_objs = parts_to_shells(part_names)
|
||||
uv_unwrap(lowpoly_objs)
|
||||
|
||||
# save blender scene
|
||||
if kwargs['blend_path'] is not None:
|
||||
if not os.path.isdir(os.path.dirname(kwargs['blend_path'])):
|
||||
os.makedirs(os.path.dirname(kwargs['blend_path']))
|
||||
bpy.ops.wm.save_as_mainfile(filepath=kwargs['blend_path'])
|
||||
|
||||
# export all objects
|
||||
if kwargs['mesh_export_path'] is not None:
|
||||
obs = bpy.context.selected_objects
|
||||
for ob in obs:
|
||||
ob.matrix_world = mathutils.Matrix()
|
||||
for ob in obs:
|
||||
ob.select_set(state=True)
|
||||
export_dae(kwargs['mesh_export_path'])
|
||||
export_col_stl(kwargs['mesh_export_path'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Convert and setup FreeCAD solid objects to 3d assets mesh files.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--fcstd_path',
|
||||
type=str,
|
||||
help='Path to source FreeCAD scene',
|
||||
required=True
|
||||
)
|
||||
parser.add_argument(
|
||||
'--tesselation_method',
|
||||
type=str,
|
||||
help='Select tesselation method: Standard or FEM.',
|
||||
default='Standard',
|
||||
required=False
|
||||
)
|
||||
parser.add_argument(
|
||||
'--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(
|
||||
'--fem_size',
|
||||
type=float,
|
||||
help='For FEM method only! Finite element size in mm',
|
||||
default=50.0,
|
||||
required=False
|
||||
)
|
||||
parser.add_argument(
|
||||
'--skiphidden',
|
||||
type=bool,
|
||||
help='Skip processing for hidden FreeCAD objects',
|
||||
default=True,
|
||||
required=False
|
||||
)
|
||||
parser.add_argument(
|
||||
'--property_forse_nonsolid',
|
||||
type=str,
|
||||
help='FreeCAD property to enable processing for nonsolid objects',
|
||||
default='Robossembler_NonSolid',
|
||||
required=False
|
||||
)
|
||||
parser.add_argument(
|
||||
'--mesh_export_path',
|
||||
type=str, help='Path for export meshes',
|
||||
required=False
|
||||
)
|
||||
parser.add_argument(
|
||||
'--blend_path',
|
||||
type=str,
|
||||
help='Path for export blend assembly file',
|
||||
required=False
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
kwargs = {key: getattr(args, key) for key in dir(args) if not key.startswith('_')}
|
||||
|
||||
cg_pipeline(**kwargs)
|
||||
|
||||
logger.info('CG Pipeline Completed!')
|
|
@ -1,165 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
DESCRIPTION.
|
||||
Convert and setup FreeCAD solid objects to 3d assets mesh files.
|
||||
Support Blender compiled as a Python Module only!
|
||||
'''
|
||||
__version__ = '0.5'
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
sys.path.append('../')
|
||||
from freecad.utils.export_freecad_scene import freecad_to_json
|
||||
|
||||
from blender.import_cad.build_blender_scene import json_to_blend
|
||||
from blender.import_cad.restruct_hierarchy_by_lcs import restruct_hierarchy
|
||||
from blender.import_cad.import_coordinate_point import lcs_json_importer
|
||||
from blender.utils.remove_collections import remove_collections
|
||||
from blender.utils.cleanup_orphan_data import cleanup_orphan_data
|
||||
from blender.utils.sdf_mesh_selector import sdf_mesh_selector
|
||||
from blender.remesh.highpoly_setup import setup_meshes
|
||||
from blender.remesh.lowpoly_setup import parts_to_shells
|
||||
|
||||
from export.dae import export_dae
|
||||
from export.collision import export_col_stl
|
||||
import bpy
|
||||
import mathutils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
'''
|
||||
IMPORT COLLECTIONS NAMIG CONVENTION:
|
||||
Import Parts - collection for mesh objects
|
||||
Import LCS - collection for location points
|
||||
Import Hierarchy - collection for hierarchy locators
|
||||
|
||||
LCS POINT'S SUFFIXES CONVENTION:
|
||||
'_in' - inlet suffix
|
||||
'_out' - outlet suffix
|
||||
'_root' - root suffix
|
||||
|
||||
CG ASSETS SUFFIXES CONVENTION:
|
||||
'_hp' - hightpoly asset (reference baking source)
|
||||
'_lp' - lowpoly asset (prepared for game engines)
|
||||
'_render' - root suffix (prepared for render engines)
|
||||
'''
|
||||
|
||||
|
||||
# COLLECTIONS NAMIG CONVENTION
|
||||
parts_col_name = 'Import Parts'
|
||||
lcs_col_name = 'Import LCS'
|
||||
hierarchy_col_name = 'Import Hierarchy'
|
||||
lowpoly_col_name = 'Lowpoly Parts'
|
||||
# LCS POINT'S SUFFIXES CONVENTION
|
||||
inlet = '_in'
|
||||
outlet = '_out'
|
||||
root = '_root'
|
||||
# CG ASSETS SUFFIXES CONVENTION
|
||||
hightpoly = '_hp'
|
||||
lowpoly = '_lp'
|
||||
render = '_render'
|
||||
|
||||
|
||||
def freecad_asset_pipeline(fcstd_path,
|
||||
tesselation_method,
|
||||
linear_deflection,
|
||||
angular_deflection,
|
||||
fem_size,
|
||||
mesh_export_path=None,
|
||||
json_path=None,
|
||||
blend_path=None,
|
||||
sdf_path=None):
|
||||
''' Setup FreeCAD scene to CG asset '''
|
||||
|
||||
# prepare blend file
|
||||
remove_collections()
|
||||
cleanup_orphan_data()
|
||||
|
||||
## convert FreeCAD scene to Blender scene
|
||||
# TODO
|
||||
objs_for_render = json_to_blend(freecad_to_json(**kwargs))
|
||||
|
||||
# restructuring hierarchy by lcs points
|
||||
lcs_objects = restruct_hierarchy()
|
||||
|
||||
# import lcs
|
||||
if not bpy.data.collections.get(lcs_col_name):
|
||||
if not bpy.data.collections[lcs_col_name].objects:
|
||||
if json_path is None:
|
||||
json_path = os.path.dirname(fcstd_path)
|
||||
for f in os.listdir(os.path.dirname(fcstd_path)):
|
||||
if f.endswith('.json'):
|
||||
json_file = os.path.join(json_path, f)
|
||||
lcs_json_importer(json_file)
|
||||
|
||||
# sdf setup WIP
|
||||
if sdf_path is not None:
|
||||
sdf_mesh_selector(sdf_path)
|
||||
|
||||
# save blender scene
|
||||
if blend_path is not None:
|
||||
if not os.path.isdir(os.path.dirname(blend_path)):
|
||||
os.makedirs(os.path.dirname(blend_path))
|
||||
bpy.ops.wm.save_as_mainfile(filepath=blend_path)
|
||||
|
||||
# retopo
|
||||
setup_meshes(objs_for_render, sharpness=True, shading=True)
|
||||
|
||||
part_names = [p.name.split(inlet)[0] for p in lcs_objects if p.name.endswith(inlet)]
|
||||
parts_to_shells(part_names)
|
||||
|
||||
# save blender scene
|
||||
if blend_path is not None:
|
||||
if not os.path.isdir(os.path.dirname(blend_path)):
|
||||
os.makedirs(os.path.dirname(blend_path))
|
||||
bpy.ops.wm.save_as_mainfile(filepath=blend_path)
|
||||
|
||||
# export all objects
|
||||
if mesh_export_path is not None:
|
||||
obs = bpy.context.selected_objects
|
||||
for ob in obs:
|
||||
ob.matrix_world = mathutils.Matrix()
|
||||
for ob in obs:
|
||||
ob.select_set(state=True)
|
||||
export_dae(mesh_export_path)
|
||||
export_col_stl(mesh_export_path)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Convert and setup FreeCAD solid objects to 3d assets mesh files.')
|
||||
parser.add_argument(
|
||||
'--fcstd_path', type=str, help='Path to source FreeCAD scene', required=True)
|
||||
parser.add_argument(
|
||||
'--tesselation_method', type=str, help='Select tesselation method: Standard or FEM.', default='Standard', required=False)
|
||||
parser.add_argument(
|
||||
'--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(
|
||||
'--fem_size', type=float, help='For FEM method only! Finite element size in mm', default=1.0, required=False)
|
||||
parser.add_argument(
|
||||
'--mesh_export_path', type=str, help='Path for export meshes', required=False)
|
||||
parser.add_argument(
|
||||
'--json_path', type=str, help='Path to DIR with coordinate points jsons', required=False)
|
||||
parser.add_argument(
|
||||
'--blend_path', type=str, help='Path for export blend assembly file', required=False)
|
||||
parser.add_argument(
|
||||
'--sdf_path', type=str, help='Path to source SDF assembly file', required=False)
|
||||
args = parser.parse_args()
|
||||
|
||||
freecad_asset_pipeline(args.fcstd_path,
|
||||
args.tesselation_method,
|
||||
args.linear_deflection,
|
||||
args.angular_deflection,
|
||||
args.fem_size,
|
||||
args.mesh_export_path,
|
||||
args.json_path,
|
||||
args.blend_path,
|
||||
args.sdf_path)
|
||||
|
||||
logger.info('Assets setup completed!')
|
26
cg/utils/cmd_proc.py
Normal file
26
cg/utils/cmd_proc.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
# 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 subprocess
|
||||
|
||||
|
||||
def cmd_proc(*args, **kwargs):
|
||||
command = list(args)
|
||||
for akey, aval in kwargs.items():
|
||||
command.append(f'--{akey}')
|
||||
command.append(str(aval))
|
||||
|
||||
return subprocess.run(command,
|
||||
check=True,
|
||||
stdout=subprocess.PIPE,
|
||||
encoding='utf-8').stdout
|
55
cg/utils/custom_parser.py
Normal file
55
cg/utils/custom_parser.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
# coding: utf-8
|
||||
# https://blender.stackexchange.com/a/134596
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
|
||||
class CustomArgumentParser(argparse.ArgumentParser):
|
||||
"""
|
||||
This class is identical to its superclass, except for the parse_args
|
||||
method (see docstring). It resolves the ambiguity generated when calling
|
||||
Blender from the CLI with a python script, and both Blender and the script
|
||||
have arguments. E.g., the following call will make Blender crash because
|
||||
it will try to process the script's -a and -b flags:
|
||||
>>> blender --python my_script.py -a 1 -b 2
|
||||
|
||||
To bypass this issue this class uses the fact that Blender will ignore all
|
||||
arguments given after a double-dash ('--'). The approach is that all
|
||||
arguments before '--' go to Blender, arguments after go to the script.
|
||||
The following calls work fine:
|
||||
>>> blender --python my_script.py -- -a 1 -b 2
|
||||
>>> blender --python my_script.py --
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _get_argv_after_doubledash():
|
||||
"""
|
||||
Given the sys.argv as a list of strings, this method returns the
|
||||
sublist right after the '--' element (if present, otherwise returns
|
||||
an empty list).
|
||||
"""
|
||||
try:
|
||||
idx = sys.argv.index("--")
|
||||
return sys.argv[idx+1:] # the list after '--'
|
||||
except ValueError as e: # '--' not in the list:
|
||||
return None
|
||||
|
||||
# overrides superclass
|
||||
def parse_args(self, args=None, namespace=None):
|
||||
"""
|
||||
This method is expected to behave identically as in the superclass,
|
||||
except that the sys.argv list will be pre-processed using
|
||||
_get_argv_after_doubledash before. See the docstring of the class for
|
||||
usage examples and details.
|
||||
"""
|
||||
return super().parse_args(
|
||||
args=args or self._get_argv_after_doubledash(),
|
||||
namespace=namespace
|
||||
)
|
||||
|
||||
def parse_known_args(self, args=None, namespace=None):
|
||||
return super().parse_known_args(
|
||||
args=args or self._get_argv_after_doubledash(),
|
||||
namespace=namespace
|
||||
)
|
Loading…
Add table
Add a link
Reference in a new issue