Skip to content

Commit

Permalink
Integrate new sheetmetal unfolder and update readme to PRs #430, #432,
Browse files Browse the repository at this point in the history
  • Loading branch information
shaise committed Jan 11, 2025
1 parent 3475076 commit 800fab5
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 99 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ Starting from FreeCAD 0.17 it can be installed via the [Addon Manager](https://g
* FreeCAD Forum announcement/discussion [thread](https://forum.freecadweb.org/viewtopic.php?f=3&t=60818)

#### Release notes:
* V0.7.00 11 Jan 2025: New SheetMetal Unfolder! by [@alexneufeld][alexneufeld].
- Unfolder backward compatibility fixes by [@GS90][GS90].
- Typo fixes by [@hasecilu][hasecilu].
* V0.6.13 25 Dec 2024: AddBase: Fix wrong shading on LinkStage.
* V0.6.12 23 Dec 2024: AddBase: Sketch remain visible while task UI is open.
* V0.6.11 20 Dec 2024: Reinstate unattended unfold command.
Expand Down Expand Up @@ -283,6 +286,7 @@ Starting from FreeCAD 0.17 it can be installed via the [Addon Manager](https://g
[adrianinsaval]: https://github.com/adrianinsaval
[robbeban]: https://github.com/robbeban
[sheetmetalman]: https://github.com/sheetmetalman
[GS90]: https://github.com/GS90
[topic82482]: https://forum.freecad.org/viewtopic.php?t=82482
[30]: https://github.com/shaise/FreeCAD_SheetMetal/issues/30
[33]: https://github.com/shaise/FreeCAD_SheetMetal/issues/33
Expand Down
28 changes: 28 additions & 0 deletions Resources/panels/SMprefs.ui
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,29 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="Gui::PrefCheckBox" name="checkBox_9">
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string>Revert To Old Unfolder</string>
</property>
<property name="prefEntry" stdset="0">
<cstring>UseOldUnfolder</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/SheetMetal</cstring>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
Expand Down Expand Up @@ -182,6 +205,11 @@
<extends>QComboBox</extends>
<header>Gui/PrefWidgets.h</header>
</customwidget>
<customwidget>
<class>Gui::PrefCheckBox</class>
<extends>QCheckBox</extends>
<header>Gui/PrefWidgets.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
Expand Down
178 changes: 104 additions & 74 deletions SheetMetalNewUnfolder.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

import FreeCAD
import Part
from Draft import makeSketch
import Draft
from FreeCAD import Matrix, Placement, Rotation, Vector
from TechDraw import projectEx as project_shape_to_plane

Expand Down Expand Up @@ -448,23 +448,47 @@ class SketchExtraction:

@staticmethod
def edges_to_sketch_object(
edges: list[Part.Edge], object_name: str
edges: list[Part.Edge],
object_name: str,
existing_sketches: list[str] = None,
color: str = "#00FF00"
) -> FreeCAD.DocumentObject:
"""Uses functionality from the Draft API to convert a list of edges into a
Sketch document object. This allows the user to more easily make small
changes to the sheet metal cutting pattern when prepping it
for fabrication."""
cleaned_up_edges = Edge2DCleanup.cleanup_sketch(edges, 0.1)
sk = makeSketch(
#cleaned_up_edges = edges

# See if there is an existing sketch with the same name, use it insted of creating
if existing_sketches is None:
existing_sketch_name = ""
else:
existing_sketch_name = next((item for item in existing_sketches if item.startswith(object_name)), "")
existing_sketch = FreeCAD.ActiveDocument.getObject(existing_sketch_name)
if existing_sketch is not None:
existing_sketch.deleteAllGeometry()

sk = Draft.makeSketch(
# NOTE: in testing, using the autoconstraint feature
# caused errors with some shapes
cleaned_up_edges,
autoconstraints=False,
addTo=None,
delete=False,
name=object_name,
addTo = existing_sketch,
delete = False,
name = object_name,
)
sk.Label = object_name
sk.recompute()

if FreeCAD.GuiUp:
rgb_color = tuple(int(color[i : i + 2], 16) for i in (1, 3, 5))
v = FreeCAD.Version()
if v[0] == '0' and int(v[1]) < 21:
rgb_color = tuple(i / 255 for i in rgb_color)
sk.ViewObject.LineColor = rgb_color
sk.ViewObject.PointColor = rgb_color

return sk

@staticmethod
Expand Down Expand Up @@ -655,8 +679,12 @@ class Edge2DCleanup:
replace bezier curves and other geometry types with lines and arcs"""

@staticmethod
def bspline_to_single_arc(curve: Part.Edge) -> tuple[Part.Edge, float]:
line = Part.makeLine(curve.firstVertex().Point, curve.lastVertex().Point)
def bspline_to_line(curve: Part.Edge) -> tuple[Part.Edge, float]:
p1 = curve.firstVertex().Point
p2 = curve.lastVertex().Point
if p1.distanceToPoint(p2) < eps:
return Part.Edge(), float("inf")
line = Part.makeLine(p1, p2)
max_err = Edge2DCleanup.check_err(curve, line)
return line, max_err

Expand All @@ -679,38 +707,51 @@ def check_err(curve1: Part.Edge, curve2: Part.Edge) -> float:
return max_err

@staticmethod
def bspline_to_line(curve: Part.Edge) -> tuple[Part.Edge, float]:
def bspline_to_arc(curve: Part.Edge) -> tuple[Part.Edge, float]:
point1 = curve.firstVertex().Point
point3 = curve.lastVertex().Point
point2 = curve.valueAt(
curve.FirstParameter + 0.5 * (curve.LastParameter - curve.FirstParameter)
)
arc = Part.Arc(point1, point2, point3).toShape().Edges[0]
point3 = curve.lastVertex().Point
if point1.distanceToPoint(point3) < eps:
# full circle
point4 = curve.valueAt(
curve.FirstParameter
+ 0.25 * (curve.LastParameter - curve.FirstParameter)
)
radius = point1.distanceToPoint(point2) / 2
center = point1 + 0.5 * (point2 - point1)
axis = (point1 - center).cross(point4 - center)
arc = Part.makeCircle(radius, center, axis)
else:
# partial circle
arc = Part.Arc(point1, point2, point3).toShape().Edges[0]
max_err = Edge2DCleanup.check_err(curve, arc)
return arc, max_err

@staticmethod
def cleanup_sketch(sketch: list[Part.Edge], tolerance: float) -> list[Part.Edge]:
new_edge_list = []
for edge in sketch:
if isinstance(edge.Curve, (Part.Line, Part.Arc)):
new_edge_list.append(edge)
else:
if isinstance(edge.Curve, Part.BSplineCurve):
bspline = edge
else:
bspline = edge.toNurbs().Edges[0]
line, max_err = Edge2DCleanup.bspline_to_line(bspline)
if max_err < tolerance:
new_edge_list.append(line)
continue
arc, max_err = Edge2DCleanup.bspline_to_single_arc(bspline)
if max_err < tolerance:
new_edge_list.append(line)
continue
new_edge_list.extend(
a.toShape().Edges[0] for a in bspline.Curve.toBiArcs(tolerance)
)
match edge.Curve.TypeId:
case "Part::GeomLine" | "Part::GeomCircle":
new_edge_list.append(edge)
case _:
if isinstance(edge.Curve, Part.BSplineCurve):
bspline = edge
else:
bspline = edge.toNurbs().Edges[0]
new_edge, max_err = Edge2DCleanup.bspline_to_line(bspline)
if max_err < tolerance:
new_edge_list.append(new_edge)
continue
new_edge, max_err = Edge2DCleanup.bspline_to_arc(bspline)
if max_err < tolerance:
new_edge_list.append(new_edge)
continue
new_edge_list.extend(
a.toShape().Edges[0] for a in bspline.Curve.toBiArcs(tolerance)
)
return new_edge_list


Expand Down Expand Up @@ -1056,76 +1097,65 @@ def unfold(
return solid, bend_lines


def gui_unfold(bac: BendAllowanceCalculator) -> None:
"""This is the main entry-point for the unfolder.
It grabs a selected sheet metal part and reference face from the active
FreeCAD document, and creates new objects showing the unfold results."""
# the user must select a single flat face of a sheet metal part in the
# active document
selection = FreeCAD.Gui.Selection.getCompleteSelection()[0]
selected_object = selection.Object
object_placement = selected_object.getGlobalPlacement().toMatrix()
shp = selected_object.Shape.transformed(object_placement.inverse())
root_face_index = int(selection.SubElementNames[0][4:]) - 1
def getUnfold(
bac: BendAllowanceCalculator, solid: Part.Feature, facename : str
) -> tuple[Part.Face, Part.Shape, Part.Compound, FreeCAD.Vector]:
object_placement = solid.Placement.toMatrix()
shp = solid.Shape.transformed(object_placement.inverse())
root_face_index = int(facename[4:]) - 1
unfolded_shape, bend_lines = unfold(shp, root_face_index, bac)
# show the unfolded solid in the active document
unfold_doc_obj = Part.show(unfolded_shape, selected_object.Label + "_Unfold")
unfold_vobj = unfold_doc_obj.ViewObject
unfold_doc_obj.Placement = Placement(object_placement)
# set appearance
unfold_vobj.ShapeAppearance = selected_object.ViewObject.ShapeAppearance
unfold_vobj.Transparency = 70 # FIXME: hardcoded value
root_normal = shp.Faces[root_face_index].normalAt(0, 0)
return shp.Faces[root_face_index], unfolded_shape, bend_lines, root_normal

def getUnfoldSketches(
selected_face: Part.Face,
unfolded_shape: Part.Shape,
bend_lines: Part.Compound,
root_normal: FreeCAD.Vector,
existing_sketches: list[str],
split_sketches: bool = False,
sketch_color: str = "#000080",
bend_sketch_color: str = "#c00000",
internal_sketch_solor: str ="#ff5733"
) -> list[Part.Feature]:
sketch_profile, inner_wires, hole_wires = SketchExtraction.extract_manually(
unfolded_shape, root_normal
)
SEPERATE_SKETCHES = False # FIXME: hardcoded value
if not SEPERATE_SKETCHES:
sketch_profile = Part.makeCompound([sketch_profile, *inner_wires, *hole_wires])
inner_wires = None
hole_wires = None
# move the sketch profiles nicely to the origin
# create transform to move the sketch profiles nicely to the origin
sketch_align_transform = SketchExtraction.move_to_origin(
sketch_profile, shp.Faces[root_face_index]
sketch_profile, selected_face
)

if not split_sketches:
sketch_profile = Part.makeCompound([sketch_profile, *inner_wires, *hole_wires, bend_lines])
inner_wires = None
hole_wires = None
bend_lines = None
sketch_profile = sketch_profile.transformed(sketch_align_transform)
# organize the unfold sketch layers in a group
sketch_doc_obj = SketchExtraction.edges_to_sketch_object(
sketch_profile.Edges, selected_object.Label + "_UnfoldProfile"
sketch_profile.Edges, "Unfold_Sketch", existing_sketches, sketch_color
)
sketch_objects_list = [
sketch_doc_obj,
]
sketch_color = (255, 0, 0, 0) # FIXME: hardcoded value
sketch_doc_obj.ViewObject.LineColor = sketch_color
sketch_doc_obj.ViewObject.PointColor = sketch_color
sketch_objects_list = [sketch_doc_obj]
# bend lines are sometimes not present
if bend_lines.Edges:
if bend_lines and bend_lines.Edges:
bend_lines = bend_lines.transformed(sketch_align_transform)
bend_lines_doc_obj = SketchExtraction.edges_to_sketch_object(
bend_lines.Edges, selected_object.Label + "_UnfoldBendLines"
bend_lines.Edges, "Unfold_Sketch_Bends", existing_sketches, bend_sketch_color
)
bend_color = (0, 255, 0, 0) # FIXME: hardcoded value
bend_lines_doc_obj.ViewObject.LineColor = bend_color
bend_lines_doc_obj.ViewObject.PointColor = bend_color
bend_lines_doc_obj.ViewObject.DrawStyle = "Dashdot"
sketch_objects_list.append(bend_lines_doc_obj)
# inner lines are sometimes not present
if inner_wires:
inner_lines = Part.makeCompound(inner_wires).transformed(sketch_align_transform)
inner_lines_doc_obj = SketchExtraction.edges_to_sketch_object(
inner_lines.Edges, selected_object.Label + "_UnfoldInnerLines"
inner_lines.Edges, "Unfold_Sketch_Internal", existing_sketches, internal_sketch_solor
)
inner_color = (0, 0, 255, 0) # FIXME: hardcoded value
inner_lines_doc_obj.ViewObject.LineColor = inner_color
inner_lines_doc_obj.ViewObject.PointColor = inner_color
sketch_objects_list.append(inner_lines_doc_obj)
if hole_wires:
hole_lines = Part.makeCompound(hole_wires).transformed(sketch_align_transform)
hole_lines_doc_obj = SketchExtraction.edges_to_sketch_object(
hole_lines.Edges, selected_object.Label + "_UnfoldHoles"
hole_lines.Edges, "Unfold_Sketch_Holes", existing_sketches, internal_sketch_solor
)
hole_color = (255, 255, 0, 0) # FIXME: hardcoded value
hole_lines_doc_obj.ViewObject.LineColor = hole_color
hole_lines_doc_obj.ViewObject.PointColor = hole_color
sketch_objects_list.append(hole_lines_doc_obj)
return sketch_objects_list
3 changes: 3 additions & 0 deletions SheetMetalTools.py
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,9 @@ def smIsOperationLegal(body, selobj):
def is_autolink_enabled():
return params.GetInt("AutoLinkBendRadius", 0)

def use_old_unfolder():
return params.GetBool("UseOldUnfolder", False)

def GetViewConfig(obj):
if smIsSketchObject(obj):
return None
Expand Down
Loading

0 comments on commit 800fab5

Please sign in to comment.