Draft: Add geometry functions for Arch Structure

* get_referenced_edges(property_value): returns the Edges referenced by the value of a App:PropertyLink, App::PropertyLinkSub or App::PropertyLinkSubList property.
* get_placement_perpendicular_to_wire(wire): returns the placement whose base is the wire's first vertex and it's z axis aligned to the wire's tangent.
* get_extended_wire(wire, offset_start, offset_end): returns a wire trimmed (negative offset) or extended (positive offset) at its first vertex, last vertex or both ends. For example:
            get_extended_wire(wire, -100.0, 0.0) -> returns a copy of the wire with its first 100 mm removed
            get_extended_wire(wire, 0.0, 100.0) -> returns a copy of the wire extended by 100 mm after it's last vertex
This commit is contained in:
alafr
2020-04-08 22:44:20 +02:00
parent 80736caa34
commit bac868a298
4 changed files with 129 additions and 10 deletions

View File

@@ -86,7 +86,8 @@ from draftgeoutils.edges import (findEdge,
is_line,
invert,
findMidpoint,
getTangent)
getTangent,
get_referenced_edges)
from draftgeoutils.faces import (concatenate,
getBoundary,
@@ -128,7 +129,9 @@ from draftgeoutils.wires import (findWires,
rebaseWire,
removeInterVertices,
cleanProjection,
tessellateProjection)
tessellateProjection,
get_placement_perpendicular_to_wire,
get_extended_wire)
# Needs wires functions
from draftgeoutils.fillets import (fillet,

View File

@@ -221,8 +221,31 @@ def getTangent(edge, from_point=None):
return None
def get_referenced_edges(property_value):
""" Returns the Edges referenced by the value of a App:PropertyLink, App::PropertyLinkList,
App::PropertyLinkSub or App::PropertyLinkSubList property. """
edges = []
if not isinstance(property_value, list):
property_value = [property_value]
for element in property_value:
if hasattr(element, "Shape") and element.Shape:
edges += shape.Edges
elif isinstance(element, tuple) and len(element) == 2:
object, subelement_names = element
if hasattr(object, "Shape") and object.Shape:
if len(subelement_names) == 1 and subelement_names[0] == "":
edges += object.Shape.Edges
else:
for subelement_name in subelement_names:
if subelement_name.startswith("Edge"):
edge_number = int(subelement_name.lstrip("Edge")) - 1
if edge_number < len(object.Shape.Edges):
edges.append(object.Shape.Edges[edge_number])
return edges
# compatibility layer
isLine = is_line
## @}
## @}

View File

@@ -59,12 +59,16 @@ def precision():
return precisionInt # return PARAMGRP.GetInt("precision", 6)
def vec(edge):
"""Return a vector from an edge or a Part.LineSegment."""
# if edge is not straight, you'll get strange results!
if isinstance(edge, Part.Shape):
return edge.Vertexes[-1].Point.sub(edge.Vertexes[0].Point)
elif isinstance(edge, Part.LineSegment):
def vec(edge, use_orientation = False):
""" vec(edge[, use_orientation]) or vec(line): returns a vector from an edge or a Part.LineSegment.
If use_orientation is True, it takes into account the edges orientation.
If edge is not straight, you'll get strange results! """
if isinstance(edge, Part.Edge):
if edge.Orientation == "Forward" or not use_orientation:
return edge.Vertexes[-1].Point.sub(edge.Vertexes[0].Point)
else:
return edge.Vertexes[0].Point.sub(edge.Vertexes[-1].Point)
elif isinstance(edge,Part.LineSegment):
return edge.EndPoint.sub(edge.StartPoint)
else:
return None

View File

@@ -435,4 +435,93 @@ def tessellateProjection(shape, seglen):
return Part.makeCompound(newedges)
## @}
def get_placement_perpendicular_to_wire(wire):
""" Returns the placement whose base is the wire's first vertex and it's z axis aligned to the wire's tangent. """
pl = FreeCAD.Placement()
if wire.Length > 0.0:
pl.Base = wire.OrderedVertexes[0].Point
first_edge = wire.OrderedEdges[0]
if first_edge.Orientation == "Forward":
zaxis = -first_edge.tangentAt(first_edge.FirstParameter)
else:
zaxis = first_edge.tangentAt(first_edge.LastParameter)
pl.Rotation = FreeCAD.Rotation(FreeCAD.Vector(1, 0, 0), FreeCAD.Vector(0, 0, 1), zaxis, "ZYX")
else:
FreeCAD.Console.PrintError("debug: get_placement_perpendicular_to_wire called with a zero-length wire.\n")
return pl
def get_extended_wire(wire, offset_start, offset_end):
""" Returns a wire trimmed (negative offset) or extended (positive offset) at its first vertex, last vertex or both ends.
get_extended_wire(wire, -100.0, 0.0) -> returns a copy of the wire with its first 100 mm removed
get_extended_wire(wire, 0.0, 100.0) -> returns a copy of the wire extended by 100 mm after it's last vertex """
if min(offset_start, offset_end, offset_start + offset_end) <= -wire.Length:
FreeCAD.Console.PrintError("debug: get_extended_wire error, wire's length insufficient for trimming.\n")
return wire
if offset_start < 0: # Trim the wire from the first vertex
offset_start = -offset_start
out_edges = []
for edge in wire.OrderedEdges:
if offset_start >= edge.Length: # Remove entire edge
offset_start -= edge.Length
elif round(offset_start, precision()) > 0: # Split edge, to remove the required length
if edge.Orientation == "Forward":
new_edge = edge.split(edge.getParameterByLength(offset_start)).OrderedEdges[1]
else:
new_edge = edge.split(edge.getParameterByLength(edge.Length - offset_start)).OrderedEdges[0]
new_edge.Placement = edge.Placement # Strangely, edge.split discards the placement and orientation
new_edge.Orientation = edge.Orientation
out_edges.append(new_edge)
offset_start = 0
else: # Keep the remaining entire edges
out_edges.append(edge)
wire = Part.Wire(out_edges)
elif offset_start > 0: # Extend the first edge along its normal
first_edge = wire.OrderedEdges[0]
if first_edge.Orientation == "Forward":
start, end = first_edge.FirstParameter, first_edge.LastParameter
vec = first_edge.tangentAt(start).multiply(offset_start)
else:
start, end = first_edge.LastParameter, first_edge.FirstParameter
vec = -first_edge.tangentAt(start).multiply(offset_start)
if geomType(first_edge) == "Line": # Replace first edge with the extended new edge
new_edge = Part.LineSegment(first_edge.valueAt(start).sub(vec), first_edge.valueAt(end)).toShape()
wire = Part.Wire([new_edge] + wire.OrderedEdges[1:])
else: # Add a straight edge before the first vertex
new_edge = Part.LineSegment(first_edge.valueAt(start).sub(vec), first_edge.valueAt(start)).toShape()
wire = Part.Wire([new_edge] + wire.OrderedEdges)
if offset_end < 0: # Trim the wire from the last vertex
offset_end = -offset_end
out_edges = []
for edge in reversed(wire.OrderedEdges):
if offset_end >= edge.Length: # Remove entire edge
offset_end -= edge.Length
elif round(offset_end, precision()) > 0: # Split edge, to remove the required length
if edge.Orientation == "Forward":
new_edge = edge.split(edge.getParameterByLength(edge.Length - offset_end)).OrderedEdges[0]
else:
new_edge = edge.split(edge.getParameterByLength(offset_end)).OrderedEdges[1]
new_edge.Placement = edge.Placement # Strangely, edge.split discards the placement and orientation
new_edge.Orientation = edge.Orientation
out_edges.insert(0, new_edge)
offset_end = 0
else: # Keep the remaining entire edges
out_edges.insert(0, edge)
wire = Part.Wire(out_edges)
elif offset_end > 0: # Extend the last edge along its normal
last_edge = wire.OrderedEdges[-1]
if last_edge.Orientation == "Forward":
start, end = last_edge.FirstParameter, last_edge.LastParameter
vec = last_edge.tangentAt(end).multiply(offset_end)
else:
start, end = last_edge.LastParameter, last_edge.FirstParameter
vec = -last_edge.tangentAt(end).multiply(offset_end)
if geomType(last_edge) == "Line": # Replace last edge with the extended new edge
new_edge = Part.LineSegment(last_edge.valueAt(start), last_edge.valueAt(end).add(vec)).toShape()
wire = Part.Wire(wire.OrderedEdges[:-1] + [new_edge])
else: # Add a straight edge after the last vertex
new_edge = Part.LineSegment(last_edge.valueAt(end), last_edge.valueAt(end).add(vec)).toShape()
wire = Part.Wire(wire.OrderedEdges + [new_edge])
return wire
## @}