From 28f8309aaca87e64c888d8726a3d8f1d6fd1413b Mon Sep 17 00:00:00 2001 From: "Frank D. Martinez M" Date: Sat, 11 Apr 2020 14:57:18 -0500 Subject: [PATCH] Custom Body --- docs/examples/example_custom_body.svg | 140 +++++++++++++++++++++++++ src/marz_body_feature.py | 59 ++++++++--- src/marz_cmd_import_body.py | 60 +---------- src/marz_cmd_import_headstock.py | 90 +--------------- src/marz_import_svg.py | 141 ++++++++++++++++++++++++++ src/marz_neck_feature.py | 2 +- 6 files changed, 333 insertions(+), 159 deletions(-) create mode 100644 docs/examples/example_custom_body.svg create mode 100644 src/marz_import_svg.py diff --git a/docs/examples/example_custom_body.svg b/docs/examples/example_custom_body.svg new file mode 100644 index 0000000..ac2daa8 --- /dev/null +++ b/docs/examples/example_custom_body.svg @@ -0,0 +1,140 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/marz_body_feature.py b/src/marz_body_feature.py index cdf2f9b..c93b2b7 100644 --- a/src/marz_body_feature.py +++ b/src/marz_body_feature.py @@ -25,13 +25,13 @@ from marz_neck_data import NeckData from marz_fretboard_data import FretboardData from marz_threading import Task -from marz_ui import (createPartBody, errorDialog, recomputeActiveDocument, +from marz_ui import (createPartBody, errorDialog, recomputeActiveDocument, Log, updatePartShape) from marz_utils import startTimeTrace from marz_vxy import angleVxy, vxy from marz_neck_feature import NeckFeature -def createBodyComp(bodyd, height, pos): +def createBodyComp(bodyd, height, pos, topThickness=0, top=False, back=False): """Create Body Top or Back Arguments: bodyd {BodyData} -- Body's data @@ -41,22 +41,55 @@ def createBodyComp(bodyd, height, pos): {Shape} -- blank """ + comp = None angle = deg(bodyd.neckAngle) contour = App.ActiveDocument.getObject('Marz_Body_Contour') + pos = Vector(pos.x, 0, pos.z) if contour: - pos = Vector(pos.x, 0, pos.z) shape = contour.Shape face = Part.Face(shape.copy()) solid = face.extrude(Vector(0, 0, height)) - solid.Placement = Placement(pos, Rotation(Vector(0,1,0), -bodyd.neckAngle)) - return solid + comp = solid else: - a = Vector(0, 0, 0) - b = Vector(-height*math.sin(angle), 0, height*math.cos(angle)) - d = Vector(-bodyd.length*math.cos(angle), 0, -bodyd.length*math.sin(angle)) - c = Vector(d.x, d.y, d.z).add(b) - points = [p.add(pos) for p in [a,b,c,d,a]] - return Part.Face(Part.makePolygon(points)).extrude(Vector(0, bodyd.width, 0)) + w2 = bodyd.width/2 + h = bodyd.length + points = [ + Vector(0,w2,0), Vector(-h,w2,0), Vector(-h,-w2,0), Vector(0,-w2,0), Vector(0,w2,0) + ] + comp = Part.Face( Part.makePolygon(points) ).extrude(Vector(0, 0, height)) + + pockets = App.ActiveDocument.getObject('Marz_Body_Pockets') + if pockets: + shape = pockets.Shape.copy() + shape.translate(Vector(0,0,height + topThickness)) + try: + comp = comp.cut(shape) + except: + Log(f'Ignoring some pockets: {pockets.Name}') + + if top: + pockets = App.ActiveDocument.getObject('Marz_Body_Pockets_Top') + if pockets: + shape = pockets.Shape.copy() + shape.translate(Vector(0,0,height)) + try: + comp = comp.cut(shape) + except: + Log(f'Ignoring some pockets: {pockets.Name}') + + if back: + pockets = App.ActiveDocument.getObject('Marz_Body_Pockets_Back') + if pockets: + shape = pockets.Shape.copy() + shape.translate(Vector(0,0,height)) + try: + comp = comp.cut(shape) + except: + Log(f'Ignoring some pockets: {pockets.Name}') + + comp.Placement = Placement(pos, Rotation(Vector(0,1,0), -bodyd.neckAngle)) + + return comp def blanks(inst, bodyd): @@ -66,10 +99,10 @@ def blanks(inst, bodyd): b = Vector(x,y,0) b = b.add(Vector(bodyd.totalThicknessWithOffset()*math.sin(angle), 0, -bodyd.totalThicknessWithOffset()*math.cos(angle))) - back = createBodyComp(bodyd, bodyd.backThickness, b) + back = createBodyComp(bodyd, bodyd.backThickness, b, bodyd.topThickness, back=True) t = Vector(b.x - bodyd.backThickness*math.sin(angle), y, b.z + bodyd.backThickness*math.cos(angle)) - top = createBodyComp(bodyd, bodyd.topThickness, t) + top = createBodyComp(bodyd, bodyd.topThickness, t, 0, top=True) # Pocket heel = NeckFeature(inst).heel(bodyd.neckd, bodyd.neckd.fbd.neckFrame.midLine) diff --git a/src/marz_cmd_import_body.py b/src/marz_cmd_import_body.py index db7a03d..15e8255 100644 --- a/src/marz_cmd_import_body.py +++ b/src/marz_cmd_import_body.py @@ -19,62 +19,7 @@ import marz_ui from marz_instrument_feature import MarzInstrument import marz_utils - -def importBodyShape(filename): - - # Defered Imports to speedup Workbench activation - import importSVG - from FreeCAD import Part, Vector - import marz_geom as geom - - # Save Working doc - workingDoc = App.ActiveDocument - - # Import SVG File - name = marz_utils.randomString(16) - importSVG.insert(filename, name) - doc = App.getDocument(name) - - # Find contour and midline by id - contour = None - midline = None - for obj in doc.Objects: - if obj.Name == 'contour': - contour = obj - elif obj.Name == 'midline': - midline = obj - - if not contour: - marz_ui.errorDialog('The SVG File does not contain any contour path. Make sure you have a path with id=contour') - return - - if not midline: - marz_ui.errorDialog('The SVG File does not contain any midline path. Make sure you have a path with id=midline') - return - - # Find contour and midline intersection - (d, vs, es) = midline.Shape.distToShape( contour.Shape ) - neckAnchor = vs[0][0] - - if d > 0.0000001: - marz_ui.errorDialog('contour path and midline path must intersect where the neck will be anchored') - return - - # Copy Shapes and Upgrade Paths to Wires - wcontour = Part.Wire( contour.Shape.copy().Edges ) - - # Restore Active Doc - App.setActiveDocument(workingDoc.Name) - App.closeDocument(doc.Name) - - # Move contour to correct position - wcontour.translate( -neckAnchor ) - - # Add contour to document - geom.addOrUpdatePart(wcontour, 'Marz_Body_Contour', 'Body Contour', visibility=False) - App.ActiveDocument.getObject('Marz_Instrument').touch() - App.ActiveDocument.recompute() - +from marz_import_svg import extractCustomShape class CmdImportBodyShape: "Import body shape from svg Command" @@ -97,7 +42,8 @@ def Activated(self): try: name = QtGui.QFileDialog.getOpenFileName(QtGui.QApplication.activeWindow(),'Select .svg file','*.svg')[0] if name: - importBodyShape(name) + #importBodyShape(name) + extractCustomShape(name, 'Marz_Body') except: marz_ui.Msg(traceback.format_exc()) diff --git a/src/marz_cmd_import_headstock.py b/src/marz_cmd_import_headstock.py index 703058d..14862e0 100644 --- a/src/marz_cmd_import_headstock.py +++ b/src/marz_cmd_import_headstock.py @@ -20,93 +20,7 @@ import marz_ui from marz_instrument_feature import MarzInstrument import marz_utils -import re - -HOLE_ID_PATTERN = re.compile(r'h\d*[_ ]+(\d+)[_ ]+(\d+)', re.IGNORECASE) - -def extractHole(obj, holes): - m = HOLE_ID_PATTERN.match(obj.Name) - if m: - start = int(m.group(1))/100 - length = int(m.group(2))/100 - holes.append((obj, start, length)) - -def importHeadstockShape(filename): - - # Defered Imports to speedup Workbench activation - import importSVG - from FreeCAD import Part, Vector - import marz_geom as geom - - # Save Working doc - workingDoc = App.ActiveDocument - - # Import SVG File - name = marz_utils.randomString(16) - importSVG.insert(filename, name) - doc = App.getDocument(name) - - # Find contour and midline by id - contour = None - midline = None - holes = [] - for obj in doc.Objects: - if obj.Name == 'contour': - contour = obj - elif obj.Name == 'midline': - midline = obj - else: - extractHole(obj, holes) - - if not contour: - marz_ui.errorDialog('The SVG File does not contain any contour path. Make sure you have a path with id=contour') - return - - if not midline: - marz_ui.errorDialog('The SVG File does not contain any midline path. Make sure you have a path with id=midline') - return - - # Find contour and midline intersection - (d, vs, es) = midline.Shape.distToShape( contour.Shape ) - neckAnchor = vs[0][0] - - if d > 0.0000001: - marz_ui.errorDialog('contour path and midline path must intersect where the neck will be anchored') - return - - # Copy Shapes and Upgrade Paths to Wires - wcontour = Part.Wire( contour.Shape.copy().Edges ) - - # Build holes compound - solids = [] - for (hole, start, length) in holes: - wire = Part.Wire( hole.Shape.copy().Edges ) - wire.translate( -neckAnchor ) - wire.translate( Vector(0,0,-start) ) - solid = Part.Face( wire ).extrude(Vector(0,0,-length)) - solids.append(solid) - - # Move contour to correct position - wcontour.translate( -neckAnchor ) - - # Restore Active Doc - App.setActiveDocument(workingDoc.Name) - App.closeDocument(doc.Name) - - if solids: - comp = None - for s in solids: - if comp: - comp = comp.fuse(s) - else: - comp = s - geom.addOrUpdatePart(comp, 'Marz_Headstock_Holes', 'Headstock Holes', visibility=False) - - # Add contour to document - geom.addOrUpdatePart(wcontour, 'Marz_Headstock_Contour', 'Headstock Contour', visibility=False) - App.ActiveDocument.getObject('Marz_Instrument').touch() - App.ActiveDocument.recompute() - +from marz_import_svg import extractCustomShape class CmdImportHeadstockShape: "Import body shape from svg Command" @@ -129,7 +43,7 @@ def Activated(self): try: name = QtGui.QFileDialog.getOpenFileName(QtGui.QApplication.activeWindow(),'Select .svg file','*.svg')[0] if name: - importHeadstockShape(name) + extractCustomShape(name, 'Marz_Headstock') except: marz_ui.Msg(traceback.format_exc()) diff --git a/src/marz_import_svg.py b/src/marz_import_svg.py new file mode 100644 index 0000000..0e33d71 --- /dev/null +++ b/src/marz_import_svg.py @@ -0,0 +1,141 @@ +# -*- coding: utf-8 -*- +""" +Marz Workbench for FreeCAD 0.19+. +https://github.com/mnesarco/MarzWorkbench +""" + +__author__ = "Frank D. Martinez. M." +__copyright__ = "Copyright 2020, Frank D. Martinez. M." +__license__ = "GPLv3" +__maintainer__ = "https://github.com/mnesarco" + + +import sys +import traceback +import os +import re + +import FreeCAD as App +import FreeCADGui as Gui + +HOLE_ID_PATTERN = re.compile(r'h([tb]?)\d*[_ ]+(\d+)[_ ]+(\d+)', re.IGNORECASE) + +class Pocket: + def __init__(self, obj, start, depth, target): + self.edges = obj.Shape.copy().Edges + self.start = start + self.depth = depth + self.target = target + +def extractPocket(obj, pockets): + """Appends (obj, startDepth, depth, part) to `holes` if id match hole pattern.""" + m = HOLE_ID_PATTERN.match(obj.Name) + if m: + part = m.group(1).lower() + start = int(m.group(2))/100 + length = int(m.group(3))/100 + pockets.append(Pocket(obj, start, length, part)) + +def extractCustomShape(filename, baseName, requireContour=True, requireMidline=True): + + # Defered Imports to speedup Workbench activation + import importSVG + from FreeCAD import Part, Vector + import marz_geom as geom + from marz_instrument_feature import MarzInstrument + import marz_utils + import marz_ui + + # Contour implies midline + requireMidline = requireMidline or requireContour + + # Save Working doc + workingDoc = App.ActiveDocument + + # Import SVG File + name = marz_utils.randomString(16) + importSVG.insert(filename, name) + doc = App.getDocument(name) + + # Find contour and midline by id + contour = None + midline = None + pockets = [] + for obj in doc.Objects: + if obj.Name == 'contour': + contour = obj + elif obj.Name == 'midline': + midline = obj + else: + extractPocket(obj, pockets) + + if not contour and requireContour: + marz_ui.errorDialog('The SVG File does not contain any contour path. Make sure you have a path with id=contour') + return + + if not midline and requireMidline: + marz_ui.errorDialog('The SVG File does not contain any midline path. Make sure you have a path with id=midline') + return + + # Load contour + wcontour = None + if requireContour: + # Find contour and midline intersection + (d, vs, es) = midline.Shape.distToShape( contour.Shape ) + anchor = vs[0][0] + # Intersection tolerance + if d > 0.0000001: + marz_ui.errorDialog('contour path and midline path must intersect where the neck will be anchored') + return + # Copy Shapes and Upgrade Paths to Wires + wcontour = Part.Wire( contour.Shape.copy().Edges ) + wcontour.translate( -anchor ) + + anchor = anchor or Vector(0,0,0) # If no reference anchor + + # Build pockets compound + solids = [] + for pocket in pockets: + wire = Part.Wire( pocket.edges ) + wire.translate( -anchor ) + wire.translate( Vector(0,0,-pocket.start) ) + solid = Part.Face( wire ).extrude(Vector(0,0,-pocket.depth)) + solids.append((pocket, solid)) + + # Restore Active Doc + App.setActiveDocument(workingDoc.Name) + App.closeDocument(doc.Name) + + # Build pockets + def merge(base, s): + if base is None: return s + else: return base.fuse(s) + + if solids: + + comp = None + compT = None + compB = None + for p, s in solids: + if p.target == 't': + compT = merge(compT, s) + elif p.target == 'b': + compB = merge(compB, s) + else: + comp = merge(comp, s) + + if comp: + geom.addOrUpdatePart(comp, baseName + '_Pockets', 'Pockets', visibility=False) + if compT: + geom.addOrUpdatePart(compT, baseName + '_Pockets_Top', 'Pockets', visibility=False) + if compB: + geom.addOrUpdatePart(compB, baseName + '_Pockets_Back', 'Pockets', visibility=False) + + # Add contour to document + if wcontour: + geom.addOrUpdatePart(wcontour, baseName + '_Contour', 'Contour', visibility=False) + + # Recalculate + App.ActiveDocument.getObject(MarzInstrument.NAME).touch() + App.ActiveDocument.recompute() + diff --git a/src/marz_neck_feature.py b/src/marz_neck_feature.py index 8dbdb90..0ebf7cf 100644 --- a/src/marz_neck_feature.py +++ b/src/marz_neck_feature.py @@ -184,7 +184,7 @@ def headstock(self, neckd, line): basehs = basehs.common(solid) # Holes - holes = App.ActiveDocument.getObject('Marz_Headstock_Holes') + holes = App.ActiveDocument.getObject('Marz_Headstock_Pockets') if angle == 0: pos = Vector(pos.x, pos.y, -self.instrument.headStock.depth) if holes: holes.Placement = Placement(pos, Rotation(Vector(0,1,0), angle))