Skip to content

Commit

Permalink
Merge branch 'v3.2.7'
Browse files Browse the repository at this point in the history
  • Loading branch information
vxlcoder committed May 20, 2022
2 parents 6dfea5b + 81f28c8 commit 880348b
Show file tree
Hide file tree
Showing 47 changed files with 1,043 additions and 465 deletions.
9 changes: 5 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

NAME = RetopoFlow

VERSION = "v3.2.6"
VERSION = "v3.2.7"

# NOTE: one of the following must be uncommented
# RELEASE = "alpha"
Expand All @@ -38,7 +38,8 @@ DEBUG_CLEANUP = $(shell pwd)/addon_common/scripts/strip_debugging.py
DOCS_REBUILD = $(shell pwd)/scripts/prep_help_for_online.py
CREATE_THUMBNAILS = $(shell pwd)/scripts/create_thumbnails.py
CGCOOKIE_BUILT = $(NAME)/.cgcookie
ZIP_FILE = $(NAME)_$(ZIP_VERSION).zip
ZIP_GH = $(NAME)_$(ZIP_VERSION)-GitHub.zip
ZIP_BM = $(NAME)_$(ZIP_VERSION)-BlenderMarket.zip


.DEFAULT_GOAL := build
Expand Down Expand Up @@ -95,7 +96,7 @@ build-github: check
# create thumbnails
cd $(BUILD_DIR)/$(NAME)/help && python3 $(CREATE_THUMBNAILS)
# zip it!
cd $(BUILD_DIR) && zip -r $(ZIP_FILE) $(NAME)
cd $(BUILD_DIR) && zip -r $(ZIP_GH) $(NAME)

@echo
@echo $(NAME)" "$(VERSION)" is ready"
Expand All @@ -114,7 +115,7 @@ build-blendermarket: check
# create thumbnails
cd $(BUILD_DIR)/$(NAME)/help && python3 $(CREATE_THUMBNAILS)
# zip it!
cd $(BUILD_DIR) && zip -r $(ZIP_FILE) $(NAME)
cd $(BUILD_DIR) && zip -r $(ZIP_BM) $(NAME)

@echo
@echo $(NAME)" "$(VERSION)" is ready"
Expand Down
101 changes: 65 additions & 36 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"author": "Jonathan Denning, Jonathan Lampel, Jonathan Williamson, Patrick Moore, Patrick Crawford, Christopher Gearhart",
"location": "View 3D > Header",
"blender": (2, 93, 0),
"version": (3, 2, 6),
"version": (3, 2, 7),
# "warning": "Alpha", # used for warning icon and text in addons panel
# "warning": "Beta",
# "warning": "Release Candidate 1",
Expand Down Expand Up @@ -243,15 +243,7 @@ def poll(cls, context):
return True

def invoke(self, context, event):
auto_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode # working around blender bug, see https://github.com/CGCookie/retopoflow/issues/786
bpy.context.preferences.edit.use_enter_edit_mode = False
for o in bpy.data.objects: o.select_set(False)
mesh = bpy.data.meshes.new('RetopoFlow')
obj = object_utils.object_data_add(context, mesh, name='RetopoFlow')
obj.select_set(True)
context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode='EDIT')
bpy.context.preferences.edit.use_enter_edit_mode = auto_edit_mode
retopoflow.RetopoFlow.create_new_target(context)
return bpy.ops.cgcookie.retopoflow('INVOKE_DEFAULT')
RF_classes += [VIEW3D_OT_RetopoFlow_NewTarget_Cursor]

Expand Down Expand Up @@ -279,17 +271,7 @@ def poll(cls, context):
return True

def invoke(self, context, event):
active = get_active_object()
auto_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode # working around blender bug, see https://github.com/CGCookie/retopoflow/issues/786
bpy.context.preferences.edit.use_enter_edit_mode = False
for o in bpy.data.objects: o.select_set(False)
mesh = bpy.data.meshes.new('RetopoFlow')
obj = object_utils.object_data_add(context, mesh, name='RetopoFlow')
obj.select_set(True)
context.view_layer.objects.active = obj
obj.matrix_world = active.matrix_world
bpy.ops.object.mode_set(mode='EDIT')
bpy.context.preferences.edit.use_enter_edit_mode = auto_edit_mode
retopoflow.RetopoFlow.create_new_target(context)
return bpy.ops.cgcookie.retopoflow('INVOKE_DEFAULT')
RF_classes += [VIEW3D_OT_RetopoFlow_NewTarget_Active]

Expand Down Expand Up @@ -331,24 +313,41 @@ class VIEW3D_OT_RetopoFlow_Tool(retopoflow.RetopoFlow):
create operator for recovering auto save
'''

class VIEW3D_OT_RetopoFlow_Recover(Operator):
bl_idname = 'cgcookie.retopoflow_recover'
bl_label = 'Recover Auto Save'
bl_description = 'Recover from RetopoFlow auto save'
class VIEW3D_OT_RetopoFlow_RecoverOpen(Operator):
bl_idname = 'cgcookie.retopoflow_recover_open'
bl_label = 'Recover: Open Last Auto Save'
bl_description = 'Recover by opening last file automatically saved by RetopoFlow'
bl_space_type = 'VIEW_3D'
bl_region_type = 'TOOLS'
bl_options = set()
rf_icon = 'rf_recover_icon'

@classmethod
def poll(cls, context):
return retopoflow.RetopoFlow.has_backup()
return retopoflow.RetopoFlow.has_auto_save()

def invoke(self, context, event):
global perform_backup_recovery
retopoflow.RetopoFlow.backup_recover()
retopoflow.RetopoFlow.recover_auto_save()
return {'FINISHED'}
RF_classes += [VIEW3D_OT_RetopoFlow_Recover]
RF_classes += [VIEW3D_OT_RetopoFlow_RecoverOpen]

class VIEW3D_OT_RetopoFlow_RecoverRevert(Operator):
bl_idname = 'cgcookie.retopoflow_recover_finish'
bl_label = 'Recover: Finish Auto Save Recovery'
bl_description = 'Finish recovering open file'
bl_space_type = 'VIEW_3D'
bl_region_type = 'TOOLS'
bl_options = set()
rf_icon = 'rf_recover_icon'

@classmethod
def poll(cls, context):
return retopoflow.RetopoFlow.can_recover()

def invoke(self, context, event):
retopoflow.RetopoFlow.recovery_revert()
return {'FINISHED'}
RF_classes += [VIEW3D_OT_RetopoFlow_RecoverRevert]


if import_succeeded:
Expand Down Expand Up @@ -388,6 +387,11 @@ def in_quadview(context):
if space.type != 'VIEW_3D': continue
if len(space.region_quadviews) > 0: return True
return False
def is_addon_folder_valid(context):
bad_chars = set(re.sub(r'[a-zA-Z0-9_]', '', __package__))
if not bad_chars: return True
print(f'Bad characters found in add-on: {bad_chars}')
return False


rf_label_extra = " (?)"
Expand All @@ -398,21 +402,22 @@ def in_quadview(context):

class VIEW3D_PT_RetopoFlow(Panel):
"""RetopoFlow Blender Menu"""
bl_label = f'RetopoFlow {retopoflow_version}{rf_label_extra}'
bl_label = 'RetopoFlow'
bl_space_type = 'VIEW_3D'
bl_region_type = 'HEADER'
# bl_ui_units_x = 100

@staticmethod
def draw_popover(self, context):
self.layout.separator()
if is_editing_target(context):
self.layout.operator('cgcookie.retopoflow', text="", icon='MOD_DATA_TRANSFER')
self.layout.popover('VIEW3D_PT_RetopoFlow')
if context.mode == 'EDIT_MESH' or context.mode == 'OBJECT':
self.layout.separator()
if is_editing_target(context):
self.layout.operator('cgcookie.retopoflow', text="", icon='MOD_DATA_TRANSFER')
self.layout.popover('VIEW3D_PT_RetopoFlow')

def draw(self, context):
layout = self.layout
layout.label(text=self.bl_label)
layout.label(text=f'RetopoFlow {retopoflow_version}{rf_label_extra}')

class VIEW3D_PT_RetopoFlow_Warnings(Panel):
bl_space_type = 'VIEW_3D'
Expand Down Expand Up @@ -457,6 +462,13 @@ def get_warnings(cls, context):
warnings.add('save: auto save is disabled')
if not retopoflow.RetopoFlow.get_auto_save_settings(context)['saved']:
warnings.add('save: unsaved blender file')
if retopoflow.RetopoFlow.can_recover():
# user directly opened an auto save file
warnings.add('save: can recover auto save')

# install checks
if not is_addon_folder_valid(context):
warnings.add('install: invalid add-on folder')

return warnings

Expand Down Expand Up @@ -528,6 +540,19 @@ def get_warning_subbox(label):
if 'save: unsaved blender file' in warnings:
box = get_warning_subbox('Auto Save / Save')
box.label(text='Unsaved Blender file', icon='DOT')
if 'save: can recover auto save' in warnings:
box = get_warning_subbox('Auto Save')
box.label(text=f'Auto Save file opened', icon='DOT')
box.operator(
'cgcookie.retopoflow_recover_finish',
text='Finish Auto Save Recovery',
icon='RECOVER_LAST',
)

# INSTALL
if 'install: invalid add-on folder' in warnings:
box = get_warning_subbox('Installation')
box.label(text=f'Invalid add-on folder name', icon='DOT')

# show button for more warning details
layout.operator('cgcookie.retopoflow_help_warnings', icon='HELP')
Expand Down Expand Up @@ -638,7 +663,11 @@ class VIEW3D_PT_RetopoFlow_AutoSave(Panel):

def draw(self, context):
layout = self.layout
layout.operator('cgcookie.retopoflow_recover', icon='RECOVER_LAST')
layout.operator(
'cgcookie.retopoflow_recover_open',
text='Open Last Auto Save',
icon='RECOVER_LAST',
)
# if retopoflow.RetopoFlow.has_backup():
# box.label(text=options['last auto save path'])

Expand Down
48 changes: 33 additions & 15 deletions addon_common/common/markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,30 +35,39 @@ class Markdown:

# markdown inline
inline_tests = {
'br': re.compile(r'<br */?> *'),
'img': re.compile(r'!\[(?P<caption>[^\]]*)\]\((?P<filename>[^) ]+)(?P<style>[^)]*)\)'),
'bold': re.compile(r'\*(?P<text>.+?)\*'),
'code': re.compile(r'`(?P<text>[^`]+)`'),
'link': re.compile(r'\[(?P<text>.+?)\]\((?P<link>.+?)\)'),
'italic': re.compile(r'_(?P<text>.+?)_'),
'checkbox': re.compile(r'<input (?P<params>.*?type="checkbox".*?)>(?P<innertext>.*?)<\/input>'),
'br': re.compile(r'<br */?> *'),
'img': re.compile(r'!\[(?P<caption>[^\]]*)\]\((?P<filename>[^) ]+)(?P<style>[^)]*)\)'),
'bold': re.compile(r'\*(?P<text>.+?)\*'),
'code': re.compile(r'`(?P<text>[^`]+)`'),
'link': re.compile(r'\[(?P<text>.+?)\]\((?P<link>.+?)\)'),
'italic': re.compile(r'_(?P<text>.+?)_'),
'html': re.compile(r'''<((?P<tagname>[a-zA-Z]+)(?P<params>( +(?P<key>[a-zA-Z_]+(=(?P<val>"[^"]*"|'[^']*'|[^"' >]+))?)))*)(>(?P<contents>.*?)(?P<closetag></\2>)|(?P<selfclose> +/>))'''),
# 'checkbox': re.compile(r'<input (?P<params>.*?type="checkbox".*?)>(?P<innertext>.*?)<\/input>'),
# 'number': re.compile(r'<input (?P<params>.*?type="number".*?)>'),
# 'button': re.compile(r'<button(?P<params>[^>]*)>(?P<innertext>.*?)<\/button>'),
# 'progress': re.compile(r'<progress(?P<params>.*?)(>(?P<innertext>.*?)<\/progress>| \/>)'),

# https://www.toptal.com/designers/htmlarrows/arrows/
'arrow': re.compile(r'&(?P<dir>uarr|darr|larr|rarr|harr|varr|uArr|dArr|lArr|rArr|hArr|vArr); *'),
'arrow': re.compile(r'&(?P<dir>uarr|darr|larr|rarr|harr|varr|uArr|dArr|lArr|rArr|hArr|vArr); *'),
}

# process markdown text similarly to Markdown
preprocessing = [
(r'<!--.*?-->', r''), # remove comments
(r'^\n*', r''), # remove leading \n
(r'\n*$', r''), # remove trailing \n
(r'\n\n\n*', r'\n\n'), # 2+ \n => \n\n
(r'---', r'—'), # em dash
(r'--', r'–'), # en dash
]

# https://stackoverflow.com/questions/3809401/what-is-a-good-regular-expression-to-match-a-url
re_url = re.compile(r'^((https?)|mailto)://([-a-zA-Z0-9@:%._\+~#=]+\.)*?[-a-zA-Z0-9@:%._+~#=]+\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&/=]*)$')

@staticmethod
def preprocess(txt):
# process message similarly to Markdown
txt = re.sub(r'<!--.*?-->', r'', txt) # remove comments
txt = re.sub(r'^\n*', r'', txt) # remove leading \n
txt = re.sub(r'\n*$', r'', txt) # remove trailing \n
txt = re.sub(r'\n\n\n*', r'\n\n', txt) # 2+ \n => \n\n
txt = re.sub(r'---', r'—', txt) # em dash
txt = re.sub(r'--', r'–', txt) # en dash
for m,r in Markdown.preprocessing:
txt = re.sub(m, r, txt)
return txt

@staticmethod
Expand All @@ -81,8 +90,10 @@ def match_line(line):
return (None, None)

re_html_char = re.compile(r'(?P<pre>[^ ]*?)(?P<code>&([a-zA-Z]+|#x?[0-9A-Fa-f]+);)(?P<post>.*)')
re_embedded_code = re.compile(r'(?P<pre>[^ `]+)(?P<code>`[^`]*`)(?P<post>.*)')
@staticmethod
def split_word(line, allow_empty_pre=False):
# search for html characters, like &nbsp;
m = Markdown.re_html_char.match(line)
if m:
pr = m.group('pre')
Expand All @@ -96,6 +107,13 @@ def split_word(line, allow_empty_pre=False):
if pr or allow_empty_pre:
return (pr, f'{co}{po}')
return (co, po)
# search for embedded code in word, like (`-`)
m = Markdown.re_embedded_code.match(line)
if m:
pr = m.group('pre')
co = m.group('code')
po = m.group('post')
return (pr, f'{co}{po}')
if ' ' not in line:
return (line,'')
i = line.index(' ') + 1
Expand Down
31 changes: 8 additions & 23 deletions addon_common/common/maths.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,7 @@ def average(vecs):
for v in vecs:
vx,vy,vz = v
ax,ay,az,ac = ax+vx,ay+vy,az+vz,ac+1
if ac == 0:
return Vec((0, 0, 0))
return Vec((ax / ac, ay / ac, az / ac))
return Vec((ax / ac, ay / ac, az / ac)) if ac else Vec((0, 0, 0))


class Index2D:
Expand Down Expand Up @@ -216,9 +214,7 @@ def average(points):
x += p.x
y += p.y
c += 1
if c == 0:
return Point2D((0, 0))
return Point2D((x / c, y / c))
return Point2D((x / c, y / c)) if c else Point2D((0, 0))

@staticmethod
def weighted_average(weight_points):
Expand All @@ -227,9 +223,7 @@ def weighted_average(weight_points):
x += p.x * w
y += p.y * w
c += w
if c == 0:
return Point2D((0, 0))
return Point2D((x / c, y / c))
return Point2D((x / c, y / c)) if c else Point2D((0, 0))


class RelPoint2D(Vector, Entity2D):
Expand Down Expand Up @@ -288,9 +282,7 @@ def average(points):
x += p.x
y += p.y
c += 1
if c == 0:
return RelPoint2D((0, 0))
return RelPoint2D((x / c, y / c))
return RelPoint2D((x / c, y / c)) if c else RelPoint2D((0, 0))

@staticmethod
def weighted_average(weight_points):
Expand All @@ -299,9 +291,7 @@ def weighted_average(weight_points):
x += p.x * w
y += p.y * w
c += w
if c == 0:
return RelPoint2D((0, 0))
return RelPoint2D((x / c, y / c))
return RelPoint2D((x / c, y / c)) if c else RelPoint2D((0, 0))
RelPoint2D.ZERO = RelPoint2D((0,0))


Expand Down Expand Up @@ -365,9 +355,7 @@ def average(points):
y += p.y
z += p.z
c += 1
if c == 0:
return Point((0, 0, 0))
return Point((x / c, y / c, z / c))
return Point((x / c, y / c, z / c)) if c else Point((0, 0, 0))

@staticmethod
def weighted_average(weight_points):
Expand All @@ -377,9 +365,7 @@ def weighted_average(weight_points):
y += p.y * w
z += p.z * w
c += w
if c == 0:
return Point((0, 0, 0))
return Point((x / c, y / c, z / c))
return Point((x / c, y / c, z / c)) if c else Point((0, 0, 0))

class Direction2D(Vector, Entity2D):
@stats_wrapper
Expand Down Expand Up @@ -498,8 +484,7 @@ def average(normals):
for n in normals:
v += n
c += 1
if c: return Normal(v)
return v
return Normal(v) if c else v


class Color(Vector):
Expand Down
Loading

0 comments on commit 880348b

Please sign in to comment.