I use this pyqgis code to create a fixed number of sample plots in forest stands/polygons. It will create a point layer in memory. Click/highlight your polygon layer, adjust the number of plots below and execute the code below. If you have polygons with very complex shapes you might want to add a counter to break the while loop or it can keep going for a long time / forever.
import numpy as np
from itertools import product
polylayer = iface.activeLayer() #Click/highlight your layer
npoints = 8 #Number of points in each polygon
pointlayer = QgsVectorLayer('Point?crs={0}'.format(polylayer.crs().authid()), 'point' , 'memory')
prov = pointlayer.dataProvider()
for poly in polylayer.getFeatures(): #For each polygon
geom = poly.geometry()
bbox = poly.geometry().boundingBox() #Create a bounding box
xmin, xmax, ymin, ymax = bbox.xMinimum(),bbox.xMaximum(),bbox.yMinimum(),bbox.yMaximum() #Find max/min x/y coordinates
success = 0
while success == 0:
featlist = []
spacing = ((geom.area()/npoints)**0.5)*np.random.uniform(0.7,1.3) #Create random spacings based on polygon area
nspacesx = np.ceil((xmax-xmin)/spacing) #Find out how many points you can fit in x direction
nspacesy = np.ceil((ymax-ymin)/spacing)
randomstart = [xmin-spacing*np.random.random(),ymin-spacing*np.random.random()] #Create a random start coordinate
xlist=[randomstart[0]+(x*spacing) for x in range(int(nspacesx)+1)] #Calculate x coordinates
ylist=[randomstart[1]+(y*spacing) for y in range(int(nspacesy)+1)] #And y
for x,y in product(xlist,ylist): #For each combination of x,y coordinate
feat = QgsFeature()
feat.setGeometry(QgsPoint(x,y)) #Create a point
featlist.append(feat)
points_inside = [1 if f.geometry().intersects(geom.buffer(-5,10)) else 0 for f in featlist] #Find the points inside the polygon (-5m from each polygon edge)
if sum(points_inside)==npoints:
featlist = [p for p,i in zip(featlist, points_inside) if i==1]
prov.addFeatures(featlist)
success = 1
QgsProject.instance().addMapLayer(pointlayer)